☰ INDEX
ASOM
Acoustic Neuroma & Vestibular Neuronitis
Acute & Chronic Pharyngitis
Acute & Chronic Tonsillitis
Adenotonsillectomy
Allergic, Vasomotor, Atrophic Rhinitis
Anatomy Of External Ear & Tympanic Membrane
Anatomy Of Larynx (Part 1)
Anatomy Of Larynx (Part 2)
Anatomy of Middle Ear (Anterior Wall)
Anatomy of Middle Ear - (Medial & Posterior Walls)
Anatomy of Sinunes
Anatomy of Throat & Neck Spaces
Anatomy of Throat & Tonsils
Assistive Devices For Hearing
Audiometry
BERA & OAE
BPPV
Cholesteatoma & Tuberculosis of Ear
Chronic Laryngitis
Complications of Sinusitis
Congenital Diseases of Larynx - Laryngomalacia
Csom
Deaf Child, Tinnitus & Hyperbaric Oxygen Therapy
Disease of External Ear
Diseases of External Nose
Electrocochleography & Cochlear Vs Retro-Cochlear
Embryology of Ear
Embryology of Nose & Sinuses
Epiglottitis & Croup
Epistaxis & Deviated Nasal Septum
Extracranial Complications & Labyrinthitis
Extracranial Complications & Mastoiditis
Facial Nerve & Vestibulocochlear Nerve Anatomy
Facial Nerve Paralysis
Fractures of Facial Bone & Csf Rhinorrhea
Glomus Tumour
Granulomatous Diseases of Nose
Hearing Loss Review & Tympanic Membrane Images
Infective Rhinosinusitis - 1 - Fungal Sinusitis
Infective Rhinosinusitis Part 2 - Bacterial Sinusitis
Instruments for EAR
Instruments for Nose & Throat
Intracranial Complications
Introduction & Examination of ENT
Juvenile Nasopharyngeal Angiofibroma
Laryngeal Carcinoma
Laryngeal Papilloma
Laryngectomy
Laryngoscopy
Laser in Ent & Photodynamic Therapy
Mastoid Anatomy
Ménière’S Disease
Nasal Polyps
Nasopharyngeal Carcinoma
Nasopharynx_ Anatomy & Diseases
Neck Dissections
Neck Mass & Thyroid Swellings
Obstructive Sleep Apnea Syndrome
Oral Cavity & Oropharynx
Otosclerosis
Pharyngeal Tumours
Physiology of Nose & Sinuses
Recent Advances in Ent Surgeries & Therapies
Salivary Gland Tumours & Parotidectomy
Serous Otitis Media (Som)
Speech Rehabilitation
Surgeries Of Otitis Media
Surgeries for Rhinosinusitis
Tracheostomy Tubes
Tracheostomy
Treatment of Otosclerosis & Stapedectomy
Tumours of Nose & Sinuses
Tuning Fork Tests
Tympanometry
Vestibular Function, Caloric Test & Fistula Tests
Vocal Cord Paralysis
Vocal Nodules, Polyps, Laryngocele
Voice Disorders
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 19</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">ASOM - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 19</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 19 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "What are the routes of spread of infection to the middle ear include?", "options": [{"label": "A", "text": "Via Eustachian Tube", "correct": false}, {"label": "B", "text": "Via External Ear", "correct": false}, {"label": "C", "text": "Bloodborne", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above Option A - Via Eustachian tube - it is the most common route infection travels via the lumen of the tube and along the subepithelial peritubular lymphatics . Option B - Via external ear -Traumatic perforation of the tympanic membrane due to any cause opens a route to a middle ear infection. Option C- Bloodborne is an uncommon route.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The Eustachian tube in infants and young children is:", "options": [{"label": "A", "text": "Shorter Narrower Vertical", "correct": false}, {"label": "B", "text": "Longer Wider Vertical", "correct": false}, {"label": "C", "text": "Shorter wider more horizontal", "correct": true}, {"label": "D", "text": "Longer wider more horizontal", "correct": false}], "correct_answer": "C. Shorter wider more horizontal", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Shorter wider more horizontal The eustachian tube in infants and young children is shorter wider and more horizontal this accounts for the higher incidence of middle ear infections in this age group.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- eustachian tube in infants is shorter, narrower and vertical. Option B- eustachian tube in infants is shorter, narrower and vertical. Option D- eustachian tube in infants is shorter, narrower and vertical.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 3-year-old child brought with fever and severe pain in the right ear with a history of throat pain three days back on examination the tympanic membrane is hyperaemic and shows characteristic cartwheel appearance of vessels the diagnosis of acute otitis media is made, what is the stage of infection?", "options": [{"label": "A", "text": "Stage of pre-suppuration", "correct": true}, {"label": "B", "text": "Stage of tubal occlusion", "correct": false}, {"label": "C", "text": "Stage Resolution", "correct": false}, {"label": "D", "text": "Stage of Complication", "correct": false}], "correct_answer": "A. Stage of pre-suppuration", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Stage of pre-suppuration This is a case of acute suppurative otitis media In the stage of the pre-suppuration tympanic membrane is hyperemic due to the invasion of microorganisms. The leash of blood vessels appears along the handle of the malleus and at the periphery of the tympanic membrane imparting it a cart-wheel appearance.</p>\n<p><strong>Highyeild:</strong></p><p>STAGE OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid(pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B - in the stage of tubal occlusion tympanic membrane will be retracted. Option C - in the stage of resolution the tympanic membrane ruptures to release pus. Option D - in the stage of complication the organism spread to various other places beyond the middle ear like the mastoid, inner ear, brain etc.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 3-year-old child brought severe pain in the right ear with a history of throat pain three days back. On examination, the tympanic membrane is hyperaemic and shows the characteristic cartwheel appearance of vessels the diagnosis of acute otitis media is made what is the treatment of choice?", "options": [{"label": "A", "text": "Antibiotics with analgesics and nasal decongestants", "correct": true}, {"label": "B", "text": "Myringotomy", "correct": false}, {"label": "C", "text": "Wait and Watch", "correct": false}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "A. Antibiotics with analgesics and nasal decongestants", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Antibiotics with analgesics and nasal decongestants It is the stage of pre-suppuration . It can be treated with oral antibiotics like Amoxiclav analgesics for pain relief and nasal decongestants to reduce the edema of the Eustachian tube .</p>\n<p><strong>Highyeild:</strong></p><p>STAGES OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid(pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B - Myringotomy is preferred in the stage of suppuration when the tympanic membrane is full and bulging and there is an impending risk of rupture. Option C - No wait and watch should be applied in acute otitis media</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The lighthouse sign is seen in", "options": [{"label": "A", "text": "Pharyngitis", "correct": false}, {"label": "B", "text": "Labyrinthitis", "correct": false}, {"label": "C", "text": "Later stages of acute otitis media", "correct": true}, {"label": "D", "text": "Furuncle", "correct": false}], "correct_answer": "C. Later stages of acute otitis media", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Later stages of acute otitis media When the bulging tympanic membrane ultimately ruptures the pus-filled in the middle ear comes out of the external auditory canal with pressure this is called pulsatile otorrhea or the lighthouse sign.</p>\n<p><strong>Highyeild:</strong></p><p>STAGES OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid (pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- In no other conditions, the pus is draining out of the external auditory canal in a pulsatile manner</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is the causative organism of acute otitis media?", "options": [{"label": "A", "text": "Haemophilus Influenza", "correct": false}, {"label": "B", "text": "Streptococcus pneumoniae", "correct": false}, {"label": "C", "text": "Moraxella", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above The most common organisms in infants and young children are Streptococcus pneumoniae , 30% Haemophilus influenzae , 20% Moraxella, and 12% other organisms including S treptococcus pyogenes, and sometimes Pseudomonas . of the external auditory canal with pressure this is called pulsatile otorrhea or the lighthouse sign.</p>\n<p><strong>Highyeild:</strong></p><p>STAGES OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid(pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In the stage of complication the acute otitis media can lead to:", "options": [{"label": "A", "text": "Petrositis", "correct": false}, {"label": "B", "text": "Labyrinthitis", "correct": false}, {"label": "C", "text": "Facial Paralysis", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above In the stage of complication acute otitis media can spread and lead to the following: Acute mastoiditis Labyrinthitis Petrositis Facial nerve paralysis Brain abscess Extradural abscess Meningitis Lateral sinus thrombophlebitis</p>\n<p><strong>Highyeild:</strong></p><p>STAGE OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid(pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Serous otitis media is also known as:", "options": [{"label": "A", "text": "Mucoid Otitis Media", "correct": false}, {"label": "B", "text": "Secretory Otitis Media", "correct": false}, {"label": "C", "text": "Glue Ear", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above Serous otitis media is synonymous with Secretory otitis media / Mucoid otitis media and Glue ear.</p>\n<p><strong>Highyeild:</strong></p><p>STAGES OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid(pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The etiology behind serous otitis media include", "options": [{"label": "A", "text": "Adenoid Hyperplasia", "correct": false}, {"label": "B", "text": "Viral Infections", "correct": false}, {"label": "C", "text": "Unresolved Otitis Media", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above Etiology behind serous otitis media include Adenoid hyperplasia Chronic tonsillitis Allergy Chronic rhinitis and sinusitis Tumours of nasopharynx Palatal defect Viral infections like an adeno and rhinoviruses of the upper respiratory tract Unresolved otitis media like ASOM.</p>\n<p><strong>Highyeild:</strong></p><p>STAGES OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, the tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid(pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 2-year-old child comes to the hospital with his mother and presents with earache. The child has a past history of acute upper respiratory infection. On examination following image of the tympanic membrane is seen. What is the most likely organism implicated in this condition?", "options": [{"label": "A", "text": "Beta Haemolytic Streptococcus", "correct": false}, {"label": "B", "text": "Streptococcus pneumoniae", "correct": true}, {"label": "C", "text": "Haemophilus influenza", "correct": false}, {"label": "D", "text": "Staphylococcus aureus", "correct": false}], "correct_answer": "B. Streptococcus pneumoniae", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685002512533-QTDE032012IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Streptococcus pneumoniae The clinical scenario given is suggestive of acute suppurative otitis media . Because following pharyngitis there is a chance of the spread of infections from the throat to the It is more common in children because of the shorter and wider eustachian tube . The most likely organism implicated is Streptococcus pneumoniae .</p>\n<p><strong>Highyeild:</strong></p><p>STAGES OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid(pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 6-year-old school-going child, Ritu, with otalgia, is diagnosed to have acute suppurative otitis media in the pre-suppuration stage. How will you manage?", "options": [{"label": "A", "text": "Antibiotics and decongestants", "correct": true}, {"label": "B", "text": "Myringotomy with antibiotics", "correct": false}, {"label": "C", "text": "Myringotomy with grommet insertion", "correct": false}, {"label": "D", "text": "Antihistaminics and decongestants", "correct": false}], "correct_answer": "A. Antibiotics and decongestants", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Antibiotics and decongestants Management of ASOM in the pre-suppuration stage involves the usage of antibiotics and decongestants.</p>\n<p><strong>Highyeild:</strong></p><p>STAGE OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid(pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B - Myringotomy is indicated only when there is an impending rupture of the tympanic membrane. Option C- Myringotomy is indicated only when there is an impending rupture of the tympanic membrane. Option D - In the pre-suppuration stage antibiotics must be used.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 5-year-old child comes to ENT OPD. You did an otologic examination of a febrile child with otalgia is shown below. What is the stage of acute suppurative otitis media seen here?", "options": [{"label": "A", "text": "Pre-Suppuration Stage", "correct": true}, {"label": "B", "text": "Complication Stage", "correct": false}, {"label": "C", "text": "Resolution Stage", "correct": false}, {"label": "D", "text": "Suppuration Stage", "correct": false}], "correct_answer": "A. Pre-Suppuration Stage", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685002512881-QTDE032016IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Pre-Suppuration Stage The otologic examination shows the stage of pre-suppuration of ASOM . During this stage, due to prolonged tubal occlusion, pyogenic organisms invade the tympanic cavity. A leash of blood vessels appears along with the handle of the malleus and at the periphery of the TM imparting it a cart-wheel appearance.</p>\n<p><strong>Highyeild:</strong></p><p>STAGES OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid(pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B- In the complication stage mastoiditis may be seen. Option C- In the resolution stage there is perforation of the tympanic membrane. Option D- In the suppuration stage there is a bulging tympanic membrane.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The yellowish tympanic membrane is seen in", "options": [{"label": "A", "text": "Early Asom", "correct": false}, {"label": "B", "text": "Glue Ear", "correct": true}, {"label": "C", "text": "Normal tympanic membrane", "correct": false}, {"label": "D", "text": "Otosclerosis", "correct": false}], "correct_answer": "B. Glue Ear", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Glue Ear In glue ear (serous otitis media) Tympanic membrane is dull opaque with loss of light reflex and appears yellow/grey/blue in colour.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Congested membrane with prominent blood vessels (cartwheel sign) is seen in the early stages of acute otitis media. Option C- Normal colour of the tympanic membrane is pearly grey. Option D - Flamingo pink colour is seen in otosclerosis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient presents with pain in the left ear, decreased hearing and thick yellowish discharge for 15 days. On examination, the tympanic membrane appears red and bulging. X-ray mastoid shows clouding of air cells. The next step in management is", "options": [{"label": "A", "text": "Antibacterial Therapy", "correct": false}, {"label": "B", "text": "Myringotomy", "correct": true}, {"label": "C", "text": "Decongestant", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "B. Myringotomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Myringotomy The patient presents with acute symptoms and the tympanic membrane is red and bulging . The probable diagnosis is Acute otitis media [ suppurative stage] . Myringotomy is the treatment for the suppurative stage in which the drum is bulging and there is acute pain . A curvilinear incision is given on the posterior-inferior quadrant of the tympanic membrane for the drainage of pus.</p>\n<p><strong>Highyeild:</strong></p><p>STAGES OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid(pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Antibacterial therapy is recommended mostly in the stage of tubal obstruction and pre-suppurative stage and continued in this stage. Option C- Decongestant is recommended in the tubal occlusion stage.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient presents with irregular fever having 2-3 peaks a day. It is accompanied by chills and rigour, headache and neck pain. Compression of the right-sided jugular vein produces engorgement of retinal vessels. Diagnostic tool for the disease:", "options": [{"label": "A", "text": "Blood Smear", "correct": false}, {"label": "B", "text": "CSF Examination", "correct": false}, {"label": "C", "text": "Contrast CT", "correct": true}, {"label": "D", "text": "X-Ray Mastoid", "correct": false}], "correct_answer": "C. Contrast CT", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Contrast CT The patient presents with intermittent high-grade fever [ picket fence fever] . On the Crow Beck test [ compression of normal-sided (right side) jugular vein there is engorgement of retinal vessels . The probable diagnosis is Lateral sinus thrombophlebitis . The diagnostic tool is a contrast CT scan or MRI which shows an Empty delta sign.</p>\n<p><strong>Highyeild:</strong></p><p>STAGES OF LATERAL SINUS THROMBOPHLEBITIS</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A - Blood smear to rule out malaria. Option B - CSF is normal except for a rise in pressure. Helps to rule out meningitis. Option D- X-ray shows clouding of mastoid air cells.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The temperature used in the Fitzgerald Hallpike test is?", "options": [{"label": "A", "text": "27°C", "correct": false}, {"label": "B", "text": "34°C", "correct": false}, {"label": "C", "text": "37°C", "correct": false}, {"label": "D", "text": "44°C", "correct": true}], "correct_answer": "D. 44°C", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>44°C Fitzgerald-Hallpike test (bithermal caloric test) . In this test, the patient lies supine with the head tilted 30 degreesforward so that the horizontal canal is vertical. Ears are irrigated for 40 sec alternately with water at 30 degrees C and 44 degrees C (i.e. 7 degrees below and above normal body temperature) and eyes are observed for the appearance of nystagmus till their endpoint.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The commonest causative organism for ASOM in 2 years the child is", "options": [{"label": "A", "text": "Pneumococcus", "correct": true}, {"label": "B", "text": "H. influenzae", "correct": false}, {"label": "C", "text": "Staphylococcus", "correct": false}, {"label": "D", "text": "Streptococcus", "correct": false}], "correct_answer": "A. Pneumococcus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Pneumococcus The most common cause of acute otitis media - Streptococcus pneumonia/pneumococcus (35–40% cases) H. influenza (25–30%) M. catarrhalis (10–20%)</p>\n<p><strong>Highyeild:</strong></p><p>STAGES OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid(pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Acute suppurative otitis media is treated by all except:", "options": [{"label": "A", "text": "Erythromycin", "correct": false}, {"label": "B", "text": "Penicillin", "correct": false}, {"label": "C", "text": "Streptomycin", "correct": true}, {"label": "D", "text": "Cephalosporin", "correct": false}], "correct_answer": "C. Streptomycin", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Streptomycin Medical management is the Treatment of choice in the case of ASOM Antibiotics of choice are: Ampicillin or amoxicillin (DOC) Other which can be used Cotrimoxazole Cefaclor Erythromycin Penicillin</p>\n<p><strong>Highyeild:</strong></p><p>STAGES OF ASOM Stage I: Tubal occlusion-blockage of the eustachian tube, tympanic membrane retracted. Stage II: Pre- Suppuration/hyperemia-cartwheel appearance of the tympanic membrane. Stage III: Suppuration- middle ear filled with fluid(pus), bulging of the tympanic membrane. Stage IV: Either Resolution/complication- tympanic membrane ruptures and symptoms subside</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "What is the indication of the procedure shown in the image?", "options": [{"label": "A", "text": "Secretory Otitis Media", "correct": true}, {"label": "B", "text": "Acute Otitis Media", "correct": false}, {"label": "C", "text": "Congenital Cholesteatoma", "correct": false}, {"label": "D", "text": "CSOM", "correct": false}], "correct_answer": "A. Secretory Otitis Media", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685002515143-QTDE032027IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Secretory Otitis Media The given Image shows grommet insertion which is usually done if myringotomy and aspiration have not helped . Grommet is inserted to provide continuous aeration to the middle ear. It is left in place for weeks or months or until spontaneously extruded. It is done in the case of secretory otitis media.</p>\n<p><strong>Highyeild:</strong></p><p>Complications of Grommet Blocked Grommet MC - due to blood clot, thick and dry secretion ↳Put drops of Saline ↳After few minutes → Suck it out Trauma to Middle ear structure: Ossicles Facial Nerve Facial Palsy Permanent perforation Extrusion of Grommet Foreign Body in Middle Ear Cholesteatoma formation</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 29 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 10</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Acoustic Neuroma & Vestibular Neuronitis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 10</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 10 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Schwannoma arises from which nerve?", "options": [{"label": "A", "text": "Vestibular part of VIIIth nerve", "correct": true}, {"label": "B", "text": "Cochlear part of VIIIth nerve", "correct": false}, {"label": "C", "text": "Vagus Nerve", "correct": false}, {"label": "D", "text": "Hypoglossal Nerve", "correct": false}], "correct_answer": "A. Vestibular part of VIIIth nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Vestibular part of VIIIth nerve The most common nerve from which vestibular schwannoma/acoustic neuroma arises is the vestibular branch of the 8th cranial nerve (inferior vestibular nerve).</p>\n<p><strong>Highyeild:</strong></p><p>Acoustic neuroma arises from the vestibular branch of the 8th cranial nerve (inferior vestibular nerve) 1st symptom/earliest symptom-sensorineural hearing loss and tinnitus. Most common presentation - is loss of corneal reflex followed by numbness of the face. HITZELBERGER SIGN - due to involvement of the 7th nerve. (Loss of sensation in EAC). TUMOURS OF CEREBELLOPONTINE ANGLE Acoustic neuroma Meningioma Epidermoid (cholesteatoma) Arachnoid cyst Schwannoma of other cranial nerves (e.g. CN V >VII > IX, X, XI) Aneurysm Glomus tumour Metastasis INVESTIGATIONS Gold standard- Gadolinium MRI. (ice cream appearance on MRI - also seen in a normal ossicular chain).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B . Schwannoma rarely arises from the cochlear part of the vestibular nerve. Option: C. Vagus nerve may be involved in vestibular schwannoma when it expands. Option: D. Hypoglossal nerve may be involved in vestibular schwannoma when the tumor is very large.</p>\n<p><strong>Extraedge:</strong></p><p>TUMOURS OF CEREBELLOPONTINE ANGLE Acoustic neuroma Meningioma Epidermoid (cholesteatoma) Arachnoid cyst Schwannoma of other cranial nerves (e.g. CN V >VII > IX, X, XI) Aneurysm Glomus tumour Metastasis</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following statements about Vestibular neuroma is not correct?", "options": [{"label": "A", "text": "Nystagmus", "correct": false}, {"label": "B", "text": "High-frequency sensorineural deafness", "correct": false}, {"label": "C", "text": "Absence of caloric response", "correct": false}, {"label": "D", "text": "Normal Corneal Reflex", "correct": true}], "correct_answer": "D. Normal Corneal Reflex", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Normal Corneal Reflex Corneal reflex is absent in acoustic neuroma due to the involvement of the 5th cranial nerve and it is the most common presentation of vestibular neuroma. So option D is not the correct statement.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A . nystagmus is seen in vestibular schwannoma as it arises from the vestibular part of the 8th cranial nerve. Hence option A is correct. Option: B . High-frequency sensorineural deafness is the earliest symptom of a vestibular neuroma. Hence option B is correct. Option: C . In acoustic neuroma – The caloric test will show diminished or absent response in 96% of patients due to vestibular involvement. Hence option C is correct.</p>\n<p><strong>Extraedge:</strong></p><p>Acoustic neuroma arises from the vestibular branch of the 8th cranial nerve (inferior vestibular nerve). 1st symptom/earliest symptom-sensorineural hearing loss and tinnitus. The most common presentation - is loss of corneal reflex due to involvement of the 5th cranial nerve. INVESTIGATIONS Gold standard - Gadolinium MRI.(ice cream appearance on MRI - also seen in the normal ossicular chain).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient is suspected to have vestibular schwannoma. The investigation of choice for its diagnosis is", "options": [{"label": "A", "text": "Contrast-enhanced CT scan", "correct": false}, {"label": "B", "text": "Gadolinium Enhanced Mri", "correct": true}, {"label": "C", "text": "SPECT", "correct": false}, {"label": "D", "text": "PET Scan", "correct": false}], "correct_answer": "B. Gadolinium Enhanced Mri", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Gadolinium Enhanced Mri MRI with gadolinium contrast is the gold standard for the diagnosis or exclusion of vestibular Schwannoma ( ice cream appearance on M RI - also seen in the normal ossicular chain).</p>\n<p><strong>Highyeild:</strong></p><p>Acoustic neuroma also called vestibular schwannoma arises from the vestibular branch of the 8th cranial nerve (inferior vestibular nerve). 1st symptom/earliest symptom-sensorineural hearing loss and tinnitus. Most common presentation - loss of corneal reflex followed by numbness of the face. HITZELBERGER SIGN - due to involvement of the 7th nerve. (Loss of sensation in EAC). INVESTIGATIONS Gold standard- Gadolinium MRI.( ice cream appearance on MRI - also seen in the normal ossicular chain).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 50 yrs old female presents with decreased hearing and ringing sensation in right ear. On examination, there is a reddish-colored mass filling the meatus. Brown sign is positive. All are diagnostic approaches to this patient except:", "options": [{"label": "A", "text": "CT Scan", "correct": false}, {"label": "B", "text": "Biopsy", "correct": true}, {"label": "C", "text": "Angiography", "correct": false}, {"label": "D", "text": "Serum Examination", "correct": false}], "correct_answer": "B. Biopsy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Biopsy The patient presents with tinnitus and decreased hearing, and a Red-colored mass filling meatus ( polyp). Brown sign ( “Pulsation sign”) is present. The probable diagnosis is a vascular tumor may be a Glomus tumor. Glomus tumor is 5 times more common in females than males. Biopsy should be not done because the tumor is highly vascular and bleed easily.</p>\n<p><strong>Highyeild:</strong></p><p>Glomus tumor is a glow-growing benign and vascular tumor Signs seen in glomus tumor Aquino sign -On pressing of the side of the tumor, the tumor shrinks in size and moves away from TM (fades away) Phelp sign Brown sign -On using Siegel’s speculum, pressure increase in the external ear and the tumor becomes pale/blanches. Rising sun sign .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A . CT scan: helps to distinguish Glomus tympanicum from Glomus jugular tumor by identification of carotic jugular spine which is eroded in the latter. Option: C . Angiography: helps to find feeding vessels and to delineate any other Glomus tumor. Option: D . Serum examination: to check the levels of catecholamines as Somi’s Glomus tumor secrete catecholamines.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Progressive loss of hearing, tinnitus, and ataxia are commonly seen in the case of:", "options": [{"label": "A", "text": "Otitis Media", "correct": false}, {"label": "B", "text": "Cerebral Glioma", "correct": false}, {"label": "C", "text": "Acoustic Neuroma", "correct": true}, {"label": "D", "text": "Ependymoma", "correct": false}], "correct_answer": "C. Acoustic Neuroma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Acoustic Neuroma Acoustic neuroma arises from the vestibular branch of the 8th cranial nerve (inferior vestibular nerve). 1st symptom/earliest symptom- sensorineural hearing loss and tinnitus . Pressure symptoms on the cerebellum are seen in large tumors. This is revealed by the finger-nose test and ataxic gait.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A .- Otitis media (OM) is any inflammation of the middle ear (see the images below), without reference to etiology or pathogenesis. Option: B: Cerebral gliomas common clinical findings on physical examination can be summarized as constituting a triad of cranial nerve deficits, long tract signs, and ataxia (of trunk and limbs). Option: D: Ependymomas are glial tumors that occur as Masses in the fourth ventricle producing Progressive lethargy, headache, nausea, and vomiting.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A middle-aged man presented to the ENT OPD with c/o hearing loss on his left side. On examination rinnes test is positive, and Webers test is lateralized to the right side. Rollover phenomena are present. He tells you that his father also has similar complaints. What is your diagnosis?", "options": [{"label": "A", "text": "Meniere’S Disease", "correct": false}, {"label": "B", "text": "Otosclerosis", "correct": false}, {"label": "C", "text": "Acoustic Neuroma", "correct": true}, {"label": "D", "text": "Impacted Wax", "correct": false}], "correct_answer": "C. Acoustic Neuroma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Acoustic Neuroma Hearing loss with rinnes positive and webers lateralized to the better side is suggestive of SNHL. Rollover is a feature of neural deafness A similar feature in his parent suggests autosomal dominant disorder.</p>\n<p><strong>Highyeild:</strong></p><p>Acoustic neuroma arises from the vestibular branch of the 8th cranial nerve (inferior vestibular nerve). 1st symptom/earliest symptom-sensorineural hearing loss and tinnitus. Most common presentation - loss of corneal reflex followed by numbness of the face. HITZELBERGER SIGN - due to involvement of the 7th nerve. (Loss of sensation in EAC). INVESTIGATIONS Gold standard - Gadolinium MRI.(ice cream appearance on MRI).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Meniers disease also causes SNHL, but rollover phenomena are not seen. It is also an autosomal dominant disorder. Option B. Otosclerosis causes B/L CHL and is more common in females. Option D. Impacted ear wax can cause U/L conductive hearing loss.</p>\n<p><strong>Extraedge:</strong></p><p>TUMOURS OF CEREBELLOPONTINE ANGLE Acoustic neuroma Meningioma Epidermoid (cholesteatoma) Arachnoid cyst Schwannoma of other cranial nerves (e.g. CN V >VII > IX, X, XI) Aneurysm Glomus tumor Metastasis</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The most common cerebellopontine angle tumor is:", "options": [{"label": "A", "text": "Acoustic Neuroma", "correct": true}, {"label": "B", "text": "Cholesteatoma", "correct": false}, {"label": "C", "text": "Meningioma", "correct": false}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "A. Acoustic Neuroma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Acoustic Neuroma The most common cerebellopontine angle tumor is Acoustic neuroma Acoustic neuroma constitutes 80% of all cerebellopontine angle tumors and 10% of all brain tumours.</p>\n<p><strong>Highyeild:</strong></p><p>TUMOURS OF CEREBELLOPONTINE ANGLE Acoustic neuroma Meningioma Epidermoid (cholesteatoma) Arachnoid cyst Schwannoma of other cranial nerves (e.g. CN V >VII > IX, X, XI) Aneurysm Glomus tumour Metastasis</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B . Cholesteatoma is a normal keratinized stratified squamous epithelium in an abnormal place ( middle ear cleft) and it is a complication of attico antral disease of CSOM. Option: C . 2nd most common cerebellopontine angle tumor is meningoma = 10%</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Acoustic neuroma commonly arises from:", "options": [{"label": "A", "text": "Inferior Vestibular Nerve", "correct": true}, {"label": "B", "text": "Superior Vestibular Nerve", "correct": false}, {"label": "C", "text": "Cochlear Nerve", "correct": false}, {"label": "D", "text": "Facial Nerve", "correct": false}], "correct_answer": "A. Inferior Vestibular Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Inferior Vestibular Nerve Vestibular schwannomas are benign, well-circumscribed, encapsulated tumors that arise from Schwann cells of the inferior part of the vestibular nerve , hence the term vestibular schwannoma or vestibular neurilemmoma.</p>\n<p><strong>Highyeild:</strong></p><p>Acoustic neuroma arises from the vestibular branch of the 8th cranial nerve (inferior vestibular nerve). 1st symptom/earliest symptom-sensorineural hearing loss and tinnitus. The most common presentation - is loss of corneal reflex followed by numbness of the face. HITZELBERGER SIGN - due to involvement of the 7th nerve. (Loss of sensation in EAC).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B . Historically, the superior vestibular nerve sheath was thought to be the site of origin, giving rise to nearly two-thirds of tumors. More recent reviews show the inferior vestibular nerve to be the predominant site of origin for these tumors. Option: C . The nerve of origin is identifiable in 33 to 74% of cases, and when clearly seen, shows the tumor originating from the inferior vestibular nerve over twice as often and in up to 94% in some reports. Option: D . Rarely the cochlear portion of the eighth cranial nerve or facial nerve is the site of schwannoma origin.</p>\n<p><strong>Extraedge:</strong></p><p>TUMOURS OF CEREBELLOPONTINE ANGLE Acoustic neuroma Meningioma Epidermoid (cholesteatoma) Arachnoid cyst Schwannoma of other cranial nerves (e.g. CN V >VII > IX, X, XI) Aneurysm Glomus tumor Metastasis INVESTIGATIONS Gold standard- Gadolinium MRI.(icecream appearance on MRI)</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The earliest sign seen in Acoustic neuroma is:", "options": [{"label": "A", "text": "Facial Weakness", "correct": false}, {"label": "B", "text": "Unilateral Deafness", "correct": false}, {"label": "C", "text": "Reduced Corneal Reflex", "correct": true}, {"label": "D", "text": "Cerebellar Signs", "correct": false}], "correct_answer": "C. Reduced Corneal Reflex", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Reduced Corneal Reflex Vth nerve is the earliest nerve to be involved. There is r educed corneal sensitivity , numbness, or paresthesia of the face. Involvement of this nerve indicates that the tumour is roughly 2.5 cm in diameter and occupies the cerebellopontine angle .</p>\n<p><strong>Highyeild:</strong></p><p>Acoustic neuroma arises from the vestibular branch of the 8th cranial nerve (inferior vestibular nerve). 1st symptom/earliest symptom-sensorineural hearing loss and tinnitus. Most common presentation- loss of corneal reflex followed by numbness of the face. HITZELBERGER SIGN- due to involvement of 7th nerve. (Loss of sensation in EAC) INVESTIGATIONS Gold standard - Gadolinium MRI.(ice cream appearance on MRI)</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A . In the 7th nerve Sensory fibres are affected early. There is hypoaesthesia of the posterior meatal wall (Hitzelberger’s sign), loss of taste (as tested by electrogustometry), and reduced lacrimation on the Schirmer test. Motor fibres are more resistant and are affected late. Option: B . unilateral deafness is the earliest symptom; not the sign. Option: D . cerebellar signs are usually a late feature due to pressure by the large tumor.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In a patient with acoustic neuroma, all are seen except", "options": [{"label": "A", "text": "Facial nerve may be involved", "correct": false}, {"label": "B", "text": "Reduced Corneal Reflex", "correct": false}, {"label": "C", "text": "Cerebellar Signs", "correct": false}, {"label": "D", "text": "Acute episode of vertigo", "correct": true}], "correct_answer": "D. Acute episode of vertigo", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Acute episode of vertigo Vestibular symptoms seen in acoustic neuroma are imbalance or unsteadiness . True vertigo is seldom seen. An acute episode of vertigo is a rare presenting feature in acoustic neuroma since it is a slow-growing tumor so there is adequate time for compensation.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Facial nerve may be involved Sensory fibres are affected early. Option: B. Reduced corneal reflex seen due to involvement of 5th cranial nerve. Option: C. Pressure symptoms on the cerebellum are seen in large tumours. This is revealed by the finger-nose test, ataxic gait, and inability to walk along a straight line with a tendency to fall to the affected side.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 20 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 3</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Acute & Chronic Pharyngitis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 3</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 3 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "All of the following are true about retropharyngeal abscess except:", "options": [{"label": "A", "text": "Confined to one side of the midline", "correct": false}, {"label": "B", "text": "Can be palpable orally by pressing the finger on the posterior pharyngeal wall", "correct": false}, {"label": "C", "text": "Lies behind the prevertebral fascia.", "correct": true}, {"label": "D", "text": "Presents with dysphagia and difficulty in breathing", "correct": false}], "correct_answer": "C. Lies behind the prevertebral fascia.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lies behind the prevertebral fascia. Retropharyngeal space lies behind the pharynx between the buccopharyngeal fascia covering pharyngeal constrictor muscles and the prevertebral fascia. It extends from the skull's base to the trachea's bifurcation.</p>\n<p><strong>Highyeild:</strong></p><p>Retropharyngeal space It lies behind the pharynx between the buccopharyngeal fascia covering pharyngeal constrictor muscles and the prevertebral fascia. It extends from the skull's base to the trachea's bifurcation. The space is divided into two lateral compartments (spaces of Gillette) by a fibrous raphe. Each lateral space contains retropharyngeal nodes, which usually disappear at 3–4 years of age. The parapharyngeal space communicates with the retropharyngeal space. Infection of the retropharyngeal space can pass down behind the oesophagus into the mediastinum.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Confined to one side of the midline is a true statement. Option: B. Can be palpable orally by pressing the finger on the posterior pharyngeal wall is a true statement. Option: D. Presents with dysphagia and difficulty breathing is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "What is your diagnosis?", "options": [{"label": "A", "text": "Epiglottitis", "correct": false}, {"label": "B", "text": "Retropharyngeal Abscess", "correct": true}, {"label": "C", "text": "Ludwig's Angina", "correct": false}, {"label": "D", "text": "TB Spine", "correct": false}], "correct_answer": "B. Retropharyngeal Abscess", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003814718-QTDE079002IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Retropharyngeal Abscess The above image shows a widening of prevertebral space with gas formation, as marked by an arrow, which is seen in the retropharyngeal abscess.</p>\n<p><strong>Highyeild:</strong></p><p>RETROPHARYNGEAL SPACE It lies behind the pharynx between the buccopharyngeal fascia covering pharyngeal constrictor muscles and the prevertebral fascia. It extends from the skull's base to the trachea's bifurcation. The space is divided into two lateral compartments (spaces of Gillette) by a fibrous raphe. Each lateral space contains retropharyngeal nodes, which usually disappear at 3–4 years of age. The parapharyngeal space communicates with the retropharyngeal space. Infection of the retropharyngeal space can pass down behind the oesophagus into the mediastinum.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. A thumb sign is seen on the X-ray in epiglottitis. Option: C. Ludwig angina is an infection of the submandibular space. Option: D. There will be an evening temperature rise in spinal TB, night sweats and chills.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A male patient complains of trismus, dysphagia and respiratory distress. On examination, he has a submental swelling with fever, tachycardia and tachypnea. What is the most likely diagnosis?", "options": [{"label": "A", "text": "Erysipelas", "correct": false}, {"label": "B", "text": "Ludwig’s Angina", "correct": true}, {"label": "C", "text": "Epiglottitis", "correct": false}, {"label": "D", "text": "Orbital Cellulitis", "correct": false}], "correct_answer": "B. Ludwig’s Angina", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003817219-QTDE079004IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ludwig’s Angina The above history and examination findings point to the diagnosis of Ludwig’s angina.</p>\n<p><strong>Highyeild:</strong></p><p>LUDWIG ANGINA It is an infection of the submandibular space. Clinical features of Ludwig’s angina: There is marked difficulty in swallowing (odynophagia) with varying degrees of trismus. When infection is localised to the sublingual space, structures in the floor of the mouth are swollen, and the tongue seems to be pushed up and back. When the infection spreads to the submaxillary space, submental and submandibular regions become swollen and tender, imparting a woody-hard feel. Usually, there is cellulitis of the tissues rather than a frank abscess. The tongue is progressively pushed upwards and backwards, threatening the airway. Laryngeal oedema may appear. Ludwig's angina in a 7-year-old child.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Erysipelas is a superficial form of cellulitis. Option: C. In epiglottitis, Sore throat and dysphagia are the common presenting symptoms in adults. Dyspnoea and stridor are the common presenting symptoms in children. They are rapidly progressive. Fever may go up to 40 °C. It is due to septicaemia. Option: D. In orbital cellulitis, Clinical features include oedema of lids, exophthalmos, chemosis of the conjunctiva and restricted eyeball movements. Vision is affected, causing partial or total loss, sometimes permanent.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 13 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 6</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Acute & Chronic Tonsillitis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 6</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 6 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 7-year-old child is brought to the ENT OPD by his parents with complaints of Fever, malaise, and sore throat for three days. On examination, there is hyperaemia of the pillars, soft palate and uvula. Tonsils are red and swollen with yellowish spots of purulent material presenting at the opening of crypts. Diagnosis of acute follicular tonsillitis is made. Which of the following is not an indication of tonsillectomy?", "options": [{"label": "A", "text": "Suspicion of Malignancy", "correct": false}, {"label": "B", "text": "Eagle’s Syndrome", "correct": false}, {"label": "C", "text": "Seven episodes in 2 years", "correct": true}, {"label": "D", "text": "Peritonsillar Abscess", "correct": false}], "correct_answer": "C. Seven episodes in 2 years", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Seven episodes in 2 years Seven or more episodes in 1 year, or five episodes per year for two years, indicates tonsillectomy.</p>\n<p><strong>Highyeild:</strong></p><p>ABSOLUTE INDICATIONS OF TONSILLECTOMY Recurrent infections of the throat . This is the most common Recurrent infections are further defined as: (a) Seven or more episodes in 1 year, or (b) Five episodes per year for two years, or (c) Three episodes per year for three years, or (d) Two weeks or more of lost school or work in 1 year. Peritonsillar abscess. In children, tonsillectomy is done 4–6 weeks after the abscess has been treated. In adults, a second attack of peritonsillar abscess forms the absolute indication. Tonsillitis which causes febrile seizures. Hypertrophy of the tonsils causing. (a) airway obstruction (sleep apnoea), (b) difficulty in deglutition and (c) interference with speech. Suspicion of malignancy . A unilaterally enlarged tonsil may be lymphoma in children and an epidermoid carcinoma in adults. An excisional biopsy is done.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. A unilaterally enlarged tonsil may be lymphoma in children and an epidermoid carcinoma in adults. An excisional biopsy is done. Option: B. An unusually long styloid process causes Eagle’s syndrome. Tonsillectomy is part of another operation (removal of the styloid process). Option: D. Peritonsillar abscess In children, tonsillectomy is done 4–6 weeks after an abscess has been treated. In adults, a second attack of peritonsillar abscess forms the absolute indication.</p>\n<p><strong>Extraedge:</strong></p><p>Usually done under general anaesthesia with endotracheal intubation. In adults, it may be done under local anaesthesia. Done in Rose’s position, i.e. patient lies supine with head extended by placing a pillow under the shoulders. A rubber ring is placed under the head to stabilize it . Hyperextension should always be avoided.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The inner Waldeyer's group of lymph nodes does not include the following:", "options": [{"label": "A", "text": "Submandibular lymph node", "correct": true}, {"label": "B", "text": "Tubal Tonsil", "correct": false}, {"label": "C", "text": "Lingual Tonsils", "correct": false}, {"label": "D", "text": "Adenoids", "correct": false}], "correct_answer": "A. Submandibular lymph node", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Submandibular lymph node Submandibular nodes do not form part of Waldeyer's lymphatic ring . They form part of the outer group of lymph nodes into which efferents from the constituents of Waldeyer's lymphatic ring may drain.</p>\n<p><strong>Highyeild:</strong></p><p>Waldeyer ring consists of: 1. Adenoids (nasopharyngeal tonsil) 2. Tubal tonsil (Fossa of Rosenmüller) 3. Lateral pharyngeal bands 4. Palatine tonsils 5. Nodules (Post pharyngeal wall) 6. Lingual tonsils</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Tubal tonsil forms the part of the Waldeyer ring. Option: C. Lingual tonsils also form part of the Waldeyer ring. Option: D. Adenoids also form part of the Waldeyer ring.</p>\n<p><strong>Extraedge:</strong></p><p>There are two potential spaces in relation to the pharynx where abscesses can form. Retropharyngeal space, situated behind the pharynx and extending from the base of skull to the bifurcation of trachea Parapharyngeal space, situated on the side of pharynx. It contains carotid vessels, jugular vein, last four cra- nial nerves and cervical sympathetic chain</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "7-year-old child has a peritonsillar abscess and presents with trismus. The best treatment is:", "options": [{"label": "A", "text": "Immediate abscess drain orally", "correct": false}, {"label": "B", "text": "Drainage Externally", "correct": false}, {"label": "C", "text": "Systemic antibiotics up to 48 hours, then drainage", "correct": true}, {"label": "D", "text": "Tracheostomy", "correct": false}], "correct_answer": "C. Systemic antibiotics up to 48 hours, then drainage", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Systemic antibiotics up to 48 hours, then drainage Treatment of quinsy with trismus includes IV antibiotics for 24-48 hours, followed by interval tonsillectomy.</p>\n<p><strong>Highyeild:</strong></p><p>PERITONSILLAR ABSCESS/QUINSY It is an abscess of peritonsillar space. Pus collects between the tonsil capsule and the superior constrictor muscle / Bed of tonsils. MC Pathogen GAHS or Strep. Pyogenes Symptoms Severe pain in Throat Dysphagia or Odynophagia Drooling of Salvia Cannot speak properly Hot potato condition Immediate I and D followed by Interval Tonsillectomy (Interval tonsillectomyTonsillectomy done After six weeks).</p>\n<p><strong>Extraedge:</strong></p><p>Immediate I and D are not done in : i) Small child with Quinsy ii) Trismus iii) Both In these, give antibiotics for 24-48 hours, followed by incision and drainage, followed by interval tonsillectomy. Peritonsillar abscess. Site of drainage is just lateral to the junction of vertical line through anterior pillar and horizontal line through base of uvula.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Most common postoperative complication of tonsillectomy is:", "options": [{"label": "A", "text": "Palatal Palsy", "correct": false}, {"label": "B", "text": "Hemorrhage", "correct": true}, {"label": "C", "text": "Injury to the uvula", "correct": false}, {"label": "D", "text": "Infection", "correct": false}], "correct_answer": "B. Hemorrhage", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Hemorrhage The most common postoperative complication of tonsillectomy is Hemorrhage.</p>\n<p><strong>Highyeild:</strong></p><p>COMPLICATIONS OF TONSILLECTOMY A. IMMEDIATE Primary haemorrhage . Occurs at the time of operation. Reactionary haemorrhage . Occurs within a period of 24 h. Injury to tonsillar pillars, uvula, soft palate, tongue or superior constrictor muscle due to bad surgical technique. Injury to teeth. Aspiration of blood. B. DELAYED Secondary haemorrhage - Usually seen between the fifth to tenth postoperative day. It is the result of sepsis and premature separation of the membrane. Infection of the tonsillar fossa may lead to para-pharyngeal abscess or otitis media. Lung complications - Aspiration of blood, mucus or tissue fragments may cause atelectasis or lung abscess. Tonsillar remnants. Tonsil tags or tissue left due to inadequate surgery may get repeatedly infected. Hypertrophy of lingual tonsil - This is a late complication and is compensatory to loss of palatine Tonsils.</p>\n<p><strong>Extraedge:</strong></p><p>Usually done under general anaesthesia with endotracheal intubation. In adults, it may be done under local anaesthesia. Done in Rose’s position, i.e. patient lies supine with head extended by placing a pillow under the shoulders. A rubber ring is placed under the head to stabilize it . Hyperextension should always be avoided.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The palatine tonsil receives its arterial supply from all of the following except:", "options": [{"label": "A", "text": "Tonsillar branch of the facial artery", "correct": false}, {"label": "B", "text": "Ascending palatine artery", "correct": false}, {"label": "C", "text": "Sphenopalatine Artery", "correct": true}, {"label": "D", "text": "Dorsal lingual artery", "correct": false}], "correct_answer": "C. Sphenopalatine Artery", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Sphenopalatine Artery Tonsil is not supplied by the sphenopalatine artery.</p>\n<p><strong>Highyeild:</strong></p><p>BLOOD SUPPLY OF TONSIL Five arteries, viz, supply the tonsils. 1. Tonsillar branch of the facial artery 2. Ascending palatine branch of the facial artery 3. Dorsal linguae branch of the lingual artery 4. Descending palatine branch of the maxillary artery 5. Tonsillar branch of the ascending pharyngeal artery</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Tonsillar branch of the facial artery supplies the tonsil. Option: B. Ascending palatine branch of the facial artery also supplies the tonsil. Option: D. Dorsal linguae branch of the lingual artery also supplies the tonsil.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All techniques are used for Tonsillectomy except:", "options": [{"label": "A", "text": "Powered", "correct": true}, {"label": "B", "text": "Coblation", "correct": false}, {"label": "C", "text": "Laser", "correct": false}, {"label": "D", "text": "Harmonic Scalpel", "correct": false}], "correct_answer": "A. Powered", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Powered Powered is not a method of tonsillectomy</p>\n<p><strong>Highyeild:</strong></p><p>Points to Remember on Tonsillectomy Position of patient during tonsillectomy: Rose position: Position of patient after tonsillectomy: Lateral position to avoid any aspiration M/C complication of tonsillectomy: Hemorrhage Method of performing tonsillectomy: Dissection and snaring method M/C cause of bleeding during tonsillectomy: Paratonsillar vein (Dennis Browne vein) Average blood loss during Adenoidectomy: 80 to 120 ml Average blood loss during tonsillectomy: 50 to 80 ml M/C arterial cause of bleeding during tonsillectomy → Tonsillar branch of facial artery (called as artery of tonsillar hemorrhage)</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Plasma-mediated ablation or dissection technique (coblation) is a method of tonsillectomy. Option: C. Laser tonsillectomy aims to reduce the size of tonsils. It is indicated in patients who are unable to tolerate general anaesthesia. Tonsils are reduced by laser ablation up to anterior pillars in stages by repeated applications. Option: D. Harmonic scalpel uses ultrasound to cut and coagulate tissues. It is a cold method that causes less tissue damage and postoperative pain than the electrocautery technique.</p>\n<p><strong>Extraedge:</strong></p><p>METHODS OF TONSILLECTOMY TECHNIQUES OF TONSILLECTOMY/ TONSILLOTOMY Cold methods Dissection and snare (most common) Guillotine method Intracapsular (capsule preserving) tonsillectomy with debrider Harmonic scalpel (ultrasound) Plasma-mediated ablation or dissection technique (coblation) Cryosurgical technique Hot methods Electrocautery Laser tonsillectomy or tonsillotomy (CO2 or KTP) Radiofrequency</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 16 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 8</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Adenotonsillectomy - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 8</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 8 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 6-year-old child presented with recurrent URTI with mouth breathing and failure to grow high arched palate and impaired hearing: Most appropriate management of this case:", "options": [{"label": "A", "text": "Tonsillectomy", "correct": false}, {"label": "B", "text": "Grommet Insertion", "correct": false}, {"label": "C", "text": "Myringotomy with grommet insertion", "correct": false}, {"label": "D", "text": "Adenoidectomy with grommet insertion", "correct": true}], "correct_answer": "D. Adenoidectomy with grommet insertion", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Adenoidectomy with grommet insertion The child has recurrent URTI with a high-arched palate and failure to grow, which indicates the child has adenoids . Since there is impaired hearing, it means the child has developed otitis media as a complication. Hence logically, the child should be treated with adenoidectomy with grommet insertion. The following lines from Scott Brown further support this. “Current practice is to perform adenoidectomy as an adjunct to insert ventilation tubes.”</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Indications for Adenoidectomy in children include:", "options": [{"label": "A", "text": "Recurrent sinusitis", "correct": false}, {"label": "B", "text": "Middle ear infection with deafness", "correct": false}, {"label": "C", "text": "Obstructive sleep apnea", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above All the above options are indications of adenoidectomy.</p>\n<p><strong>Highyeild:</strong></p><p>Indications for Adenoidectomy Adenoid hypertrophy causes snoring, mouth breathing, sleep apnoea syndrome or speech abnormalities, i.e. (rhinolalia clausa). Recurrent rhinosinusitis. Chronic otitis media with effusion associated with adenoiditis/adenoid hyperplasia. Recurrent ear discharge in benign CSOM associated with adenoiditis/adenoid hyperplasia. Dental malocclusion. Adenoidectomy does not correct dental abnormalities but will prevent their recurrence after orthodontic treatment.</p>\n<p><strong>Extraedge:</strong></p><p>CONTRAINDICATIONS OF ADENOIDECTOMY Cleft palate or submucous palate. Removal of adenoids causes velopharyngeal insufficiency in such cases. Haemorrhagic diathesis. Acute infection of upper respiratory tract.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 5-year-old patient is scheduled for a tonsillectomy. On the day of surgery, he had a runny nose, a temperature of 37.5°C and a dry cough. Which of the following should be the most appropriate decision for surgery?", "options": [{"label": "A", "text": "Surgery should be cancelled", "correct": false}, {"label": "B", "text": "Can proceed for surgery if chest is clear and there is no history of asthma", "correct": false}, {"label": "C", "text": "Should get an X-ray chest before proceeding with surgery", "correct": false}, {"label": "D", "text": "Cancel surgery for three weeks, and the patient to be on antibiotic", "correct": true}], "correct_answer": "D. Cancel surgery for three weeks, and the patient to be on antibiotic", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cancel surgery for three weeks, and the patient to be on antibiotic “There are no absolute contraindications to tonsillectomy . As such, tonsillectomy is an elective operation and should not be undertaken in the presence of respiratory tract infections or during incubation after contact with one of the infectious diseases if there are tonsillar inflammations. It is much safer to wait some three weeks after an acute inflammatory illness before operating because of the greatly increased risk of postoperative haemorrhage.” – Turner 10/e, p 365,366</p>\n<p><strong>Highyeild:</strong></p><p>CONTRAINDICATIONS OF TONSILLECTOMY Haemoglobin level less than ten g%. Presence of acute infection in the upper respiratory tract, even acute tonsillitis. Bleeding is more in the presence of acute infection. Children under three years of age. They are poor surgical risks. Overt or submucous cleft palate. Von Willebrand disease. Bleeding disorders, e.g. leukaemia, purpura, aplastic anaemia, haemophilia or sickle cell disease. At the time of the epidemic of polio. Uncontrolled systemic disease, e.g. diabetes, cardiac disease, hypertension or asthma. Tonsillectomy is avoided during the period of menses.</p>\n<p><strong>Extraedge:</strong></p><p>Usually done under general anaesthesia with endotracheal intubation. In adults, it may be done under local anaesthesia. Done in Rose’s position, i.e. patient lies supine with head extended by placing a pillow under the shoulders. A rubber ring is placed under the head to stabilize it . Hyperextension should always be avoided.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Secondary haemorrhage after tonsillectomy develops:", "options": [{"label": "A", "text": "Within 12 hrs", "correct": false}, {"label": "B", "text": "Within 24 hrs", "correct": false}, {"label": "C", "text": "Within six days", "correct": true}, {"label": "D", "text": "Within one month", "correct": false}], "correct_answer": "C. Within six days", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Within six days Secondary haemorrhage is usually seen between the fifth to tenth postoperative day . It is the result of sepsis and premature separation of the membrane . Usually, it is heralded by bloodstained sputum but may be profuse.</p>\n<p><strong>Highyeild:</strong></p><p>Primary haemorrhage - Occurs at the time of operation. It can be controlled by pressure, ligation or electrocoagulation of the bleeding vessels. Reactionary haemorrhage - Occurs within 24 h and can be controlled by simple measures such as removing the clot, applying pressure or vasoconstrictor. The presence of a clot prevents the clipping action of the superior constrictor muscle on the vessels which pass through it. Secondary haemorrhage - Usually seen between the fifth to tenth postoperative day. It is the result of sepsis and premature separation of the membrane. Usually, it is heralded by bloodstained sputum but may be profuse.</p>\n<p><strong>Extraedge:</strong></p><p>TECHNIQUES OF TONSILLECTOMY/ TONSILLOTOMY Cold methods Dissection and snare (most common) Guillotine method Intracapsular (capsule preserving) tonsillectomy with debrider Harmonic scalpel (ultrasound) Plasma-mediated ablation or dissection technique (coblation) Cryosurgical technique Hot methods Electrocautery Laser tonsillectomy or tonsillotomy (CO2 or KTP) Radiofrequency</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 15 year of age presents with haemorrhage 5 hours after tonsillectomy. Treatment of choice is?", "options": [{"label": "A", "text": "External Gauze Packing", "correct": false}, {"label": "B", "text": "Antibiotics and mouthwash", "correct": false}, {"label": "C", "text": "Irrigation With Saline", "correct": false}, {"label": "D", "text": "Reopen Immediately", "correct": true}], "correct_answer": "D. Reopen Immediately", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Reopen Immediately Primary Haemorrhage occurs within 1st 24hrs postoperatively . It requires surgical intervention. So, the above question says that haemorrhage has occurred after 5hrs of tonsillectomy, i.e. it is primary and should be surgically intervened and reopened immediately.</p>\n<p><strong>Highyeild:</strong></p><p>Primary haemorrhage - Occurs at the time of operation. It can be controlled by pressure, ligation or electrocoagulation of the bleeding vessels. Reactionary haemorrhage - Occurs within 24 h and can be controlled by simple measures such as removing the clot, applying pressure or vasoconstrictor. The presence of a clot prevents the clipping action of the superior constrictor muscle on the vessels which pass through it. Secondary haemorrhage - Usually seen between the fifth to tenth postoperative day. It is the result of sepsis and premature separation of the membrane. Usually, it is heralded by bloodstained sputum but may be profuse.</p>\n<p><strong>Extraedge:</strong></p><p>TECHNIQUES OF TONSILLECTOMY/ TONSILLOTOMY Cold methods Dissection and snare (most common) Guillotine method Intracapsular (capsule preserving) tonsillectomy with debrider Harmonic scalpel (ultrasound) Plasma-mediated ablation or dissection technique (coblation) Cryosurgical technique Hot methods Electrocautery Laser tonsillectomy or tonsillotomy (CO2 or KTP) Radiofrequency</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 14-year-old girl presented with slow progressive difficulty in respiration and deglutition and five episodes of a sore throat for the past two years. A pharyngeal examination showed that the right tonsil was about three times larger than the left. Tonsillectomy was suggested for this patient. Which among the following is not an indication of tonsillectomy?", "options": [{"label": "A", "text": "Hypertrophy of Tonsil", "correct": false}, {"label": "B", "text": "Peritonsillar Abscess", "correct": false}, {"label": "C", "text": "Chronic tonsillitis unresponsive to treatment.", "correct": false}, {"label": "D", "text": "Acute tonsillitis with upper respiratory tract infection.", "correct": true}], "correct_answer": "D. Acute tonsillitis with upper respiratory tract infection.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Acute tonsillitis with upper respiratory tract infection. Acute infection in the upper respiratory tract, even acute tonsillitis, is a contraindication for tonsillectomy . Bleeding is more in the presence of acute infection.</p>\n<p><strong>Highyeild:</strong></p><p>Indications of tonsillectomy A. ABSOLUTE Recurrent infections of the throat . This is the most common Recurrent infections are further defined as: (a) Seven or more episodes in 1 year, or (b) Five episodes per year for two years, or (c) Three episodes per year for three years, or (d) Two Weeks Or more of lost school or work in 1 year. Peritonsillar abscess . In children, tonsillectomy is done 4–6 weeks after the abscess has been treated. In adults, a second attack of peritonsillar abscess forms the absolute indication. Tonsillitis which causes febrile seizures. Hypertrophy of the tonsils causing. (a) airway obstruction (sleep apnoea), (b) difficulty in deglutition and (c) interference with speech. Suspicion of malignancy . A unilaterally enlarged tonsil may be lymphoma in children and an epidermoid carcinoma in adults. An excisional biopsy is done. B. RELATIVE Diphtheria carriers, who do not respond to antibiotics. Streptococcal carriers, which may be the source of infection to others. Chronic tonsillitis with bad taste or halitosis is unresponsive to medical treatment. Recurrent streptococcal tonsillitis in a patient with valvular heart disease.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Hypertrophy of the Tonsil is an indication of tonsillectomy. OPTION B- Peritonsillar Abscess is also an indication of tonsillectomy. OPTION C- chronic tonsillitis unresponsive to treatment is also an indication of tonsillectomy.</p>\n<p><strong>Extraedge:</strong></p><p>CONTRAINDICATIONS Haemoglobin level less than 10 g%. Presence of acute infection in upper respiratory tract, even acute tonsillitis. Bleeding is more in the presence of acute infection. Children under 3 years of age. They are poor surgical risks. Overt or submucous cleft palate. Von Willebrand disease. Bleeding disorders, e.g. leukaemia, purpura, aplastic anaemia, haemophilia or sickle cell disease. At the time of epidemic of polio. Uncontrolled systemic disease, e.g. diabetes, cardiac disease, hypertension or asthma. Tonsillectomy is avoided during the period of menses.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 10-year-old boy complained of neck pain and limited range of neck movement after adenoidectomy. What would be the complication of adenoidectomy in this case?", "options": [{"label": "A", "text": "Grisel’ S Syndrome", "correct": true}, {"label": "B", "text": "Velopharyngeal Insufficiency", "correct": false}, {"label": "C", "text": "Hemorrhage", "correct": false}, {"label": "D", "text": "Injury to the eustachian tube opening", "correct": false}], "correct_answer": "A. Grisel’ S Syndrome", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Grisel’ S Syndrome In Grisel’s syndrome, the patient complains of neck pain and develops torticollis . Mostly it is due to spasms of paraspinal muscles , but it can be due to atlantoaxial dislocation requiring a cervical collar and even traction.</p>\n<p><strong>Highyeild:</strong></p><p>GRISEL SYNDROME It is a complication of adenoidectomy. The patient complains of neck pain and develops torticollis. Mostly it is due to spasms of paraspinal muscles, but it can be due to atlantoaxial dislocation requiring a cervical collar and even traction.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - OPTION B- Velopharyngeal insufficiency is characterised by hypernasal speech, nasal emission and turbulence, and in some cases, nasal regurgitation of fluids. OPTION C- Haemorrhage is usually seen during the immediate postoperative period. The nose and mouth may be full of blood, or the only indication may be vomitus of dark-coloured blood, which the patient had been swallowing gradually in the postoperative period. A rising pulse rate is another indicator. OPTION D- Adenoidectomy is a common obstructive sleep apnoea and nasal obstruction procedure. Curettage is the most common technique but is associated with complications (mucosal trauma, bleeding) that may cause eustachian tube dysfunction (ETD).</p>\n<p><strong>Extraedge:</strong></p><p>It is a nontraumatic atlantoaxial subluxation, usually secondary to an infection or an inflammation at the head and neck region. It can be observed after surgery of the head and neck region.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 13-year-old boy had bleeding 12 hrs after tonsillectomy. Immediately vasoconstrictors were given by the concerned doctors. What is the type of haemorrhage in this situation?", "options": [{"label": "A", "text": "Primary Haemorrhage", "correct": false}, {"label": "B", "text": "Secondary Haemorrhage", "correct": false}, {"label": "C", "text": "Reactionary Haemorrhage", "correct": true}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "C. Reactionary Haemorrhage", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Reactionary Haemorrhage Reactionary haemorrhage occurs within 24 h and can be controlled by simple measures such as removing the clot, applying pressure or vasoconstrictor . The presence of a clot prevents the clipping action of the superior constrictor muscle on the vessels which pass through it (compare postpartum uterine bleeding). If the above measures fail, ligation or electrocoagulation of the bleeding vessels can be done under general anaesthesia.</p>\n<p><strong>Highyeild:</strong></p><p>1. Primary haemorrhage - Occurs at the time of operation. It can be controlled by pressure, ligation or electrocoagulation of the bleeding vessels. Reactionary haemorrhage - Occurs within 24 h and can be controlled by simple measures such as removing the clot, applying pressure or vasoconstrictor. The presence of a clot prevents the clipping action of the superior constrictor muscle on the vessels which pass through it. Secondary haemorrhage - Usually seen between the fifth to tenth postoperative day. It is the result of sepsis and premature separation of the membrane. Usually, it is heralded by bloodstained sputum but may be profuse.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - OPTION A- Primary Haemorrhage occurs at the time of operation. OPTION B- Secondary Haemorrhage is usually seen between the fifth to tenth postoperative day. It is the result of sepsis and premature separation of the membrane.</p>\n<p><strong>Extraedge:</strong></p><p>TECHNIQUES OF TONSILLECTOMY/ TONSILLOTOMY Cold methods Dissection and snare (most common) Guillotine method Intracapsular (capsule preserving) tonsillectomy with debrider Harmonic scalpel (ultrasound) Plasma-mediated ablation or dissection technique (coblation) Cryosurgical technique Hot methods Electrocautery Laser tonsillectomy or tonsillotomy (CO2 or KTP) Radiofrequency</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 18 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 12</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Allergic, Vasomotor, Atrophic Rhinitis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 12</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 12 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A first-year junior resident in the ENT department is examining a patient with nasal obstruction and discharge. The anterior rhinoscopy shows a mulberry appearance of the inferior turbinate. What is the most likely diagnosis?", "options": [{"label": "A", "text": "Atrophic Rhinitis", "correct": false}, {"label": "B", "text": "Coryza", "correct": false}, {"label": "C", "text": "Hypertrophic Rhinitis", "correct": true}, {"label": "D", "text": "Vasomotor Rhinitis", "correct": false}], "correct_answer": "C. Hypertrophic Rhinitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Hypertrophic Rhinitis The mulberry appearance of the turbinate is seen in patients with hypertrophic rhinitis.</p>\n<p><strong>Highyeild:</strong></p><p>HYPERTROPHIC RHINITIS The thickening of the mucosa, submucosa, seromucous glands, periosteum, and bone characterizes it. Common causes are recurrent nasal infections, chronic sinusitis, and chronic nasal mucosa irritation due to smoking. Nasal obstruction is the predominant symptom. Nasal discharge is thick and sticky. Examination shows hypertrophy of turbinates. Turbinal mucosa is thick and does not pit on pressure. It shows slight shrinkage with vasoconstrictor drugs due to underlying fibrosis. Maximum changes are seen in the inferior turbinate. It may be hypertrophied or only at the anterior end, posterior end, or along the inferior border, giving it a mulberry appearance.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A. The nasal cavities in atrophic rhinitis are roomy and full of foul-smelling crusts. Option: B. Coryza is the common cold. Option: D. In Vasomotor Rhinitis, Nasal mucosa over the turbinates is generally congested and hypertrophic.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are a senior resident doctor in the department of ENT. An adolescent girl with atrophic rhinitis is using kemicetine antiozaena solution. Which of the following is not a component of this solution?", "options": [{"label": "A", "text": "Chloramphenicol", "correct": false}, {"label": "B", "text": "Estradiol", "correct": false}, {"label": "C", "text": "Vitamin D2", "correct": false}, {"label": "D", "text": "Sodium Bicarbonate", "correct": true}], "correct_answer": "D. Sodium Bicarbonate", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Sodium Bicarbonate Sodium bicarbonate is not a component of the emetine anti-ozaena solution. It is used in the alkaline nasal solution. It is used in the medical management of atrophic rhinitis.</p>\n<p><strong>Highyeild:</strong></p><p>The components of emetine anti ozaena solution are: Vitamin D2 Oestradiol Chloramphenicol</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 23-year-old female patient from a rural area presented with foul-smelling nasal discharge, nasal crusts, and epistaxis. She was diagnosed with atrophic rhinitis. On examination, larvae were seen in the nasal cavity. Which of the following is used in the treatment for this patient?", "options": [{"label": "A", "text": "Chloroform diluted with water", "correct": true}, {"label": "B", "text": "Liquid Paraffin", "correct": false}, {"label": "C", "text": "Lignocaine Spray", "correct": false}, {"label": "D", "text": "Systemic Antibiotics", "correct": false}], "correct_answer": "A. Chloroform diluted with water", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Chloroform diluted with water Myiasis (larvae in the nasal cavity) is a complication of atrophic rhinitis. It is treated with chloroform diluted with water.</p>\n<p><strong>Highyeild:</strong></p><p>Treatment of maggots in atrophic rhinitis All visible maggots should be picked up with forceps. Installation of chloroform water and oil kills them. The nasal douche with warm saline removes slough, crusts, and dead maggots. (A) The maggot. (B) The fly responsible for maggots.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 33 years old woman with nasal discharge and crusting is diagnosed with ozaena. Which of the following organisms is most commonly associated with this condition?", "options": [{"label": "A", "text": "Coccobacillus foetidus ozaenae", "correct": false}, {"label": "B", "text": "Streptococcus Pneumonia", "correct": false}, {"label": "C", "text": "Klebsiella Ozaena", "correct": true}, {"label": "D", "text": "Klebsiella Rhinoscleroma", "correct": false}], "correct_answer": "C. Klebsiella Ozaena", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Klebsiella Ozaena The most common organism causing primary atrophic rhinitis is Klebsiella ozaena.</p>\n<p><strong>Highyeild:</strong></p><p>ATROPHIC RHINITIS/OZAENA The most common organism causing primary atrophic rhinitis is Klebsiella ozaena. It is a chronic nose inflammation characterized by atrophy of nasal mucosa and turbinate bones. The nasal cavities are roomy and full of foul-smelling crusts The disease is commonly seen in females and starts around puberty. The nose has a foul smell, making the patient a social outcast. However, the patient is unaware of the scent due to marked anosmia ( merciful anosmia ), which accompanies these degenerative changes.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "What is the main reason for nasal obstruction in a 34-year-old male patient with atrophic rhinitis?", "options": [{"label": "A", "text": "Hypertrophy of Turbinates", "correct": false}, {"label": "B", "text": "Crust Formation", "correct": true}, {"label": "C", "text": "Excessive Synechiae", "correct": false}, {"label": "D", "text": "Polyps", "correct": false}], "correct_answer": "B. Crust Formation", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Crust Formation Nasal obstruction in a patient with atrophic rhinitis is due to excessive crust formation. Turbinates are atrophied here.</p>\n<p><strong>Highyeild:</strong></p><p>ATROPHIC RHINITIS Symptoms: Foul smell from the nose Nasal obstruction despite roomy chambers due to large crusts filling the nasal cavity Epistaxis on removing the crusts Anosmia due to degeneration of nerve elements (merciful anosmia). Greenish or greyish-black dry crusts covering the turbinates and septum After removal of crusts, nasal cavities appear roomy with atrophy of turbinates The nasal mucosa seems pale. Atrophic rhinitis is more common in females and usually starts at puberty. It is a bilateral condition.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 44-year-old man complained of excessive nasal discharge, sneezing, and nasal obstruction. There is no history of allergen exposure or asthma in the family. You suspect vasomotor rhinitis. Which of the following statements is true regarding this condition?", "options": [{"label": "A", "text": "It is an allergic condition.", "correct": false}, {"label": "B", "text": "It occurs due to sympathetic overactivity.", "correct": false}, {"label": "C", "text": "Vidian neurectomy is a treatment option", "correct": true}, {"label": "D", "text": "Pale and atrophied turbinates are seen.", "correct": false}], "correct_answer": "C. Vidian neurectomy is a treatment option", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Vidian neurectomy is a treatment option A vidian neurectomy is a treatment option for vasomotor rhinitis.</p>\n<p><strong>Highyeild:</strong></p><p>VASOMOTOR RHINITIS Vasomotor rhinitis is a non-allergic condition due to parasympathetic overactivity, causing vasodilatation and nasal mucosa congestion. The vidian nerve carries the parasympathetic supply to the nose. The usual presentation is excessive rhinorrhea, sneezing, and nasal obstruction, similar to allergic rhinitis. The turbinates will be congested and edematous. Treatment includes antihistamines, oral, nasal decongestants, and topical steroids. In case of failed medical therapy, vidian neurectomy is done.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option A. Vasomotor rhinitis is a non-allergic condition. O ption B. Vasomotor rhinitis occurs due to parasympathetic overactivity, O ption D. turbinates will be congested and edematous.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 33-year-old woman presents with complaints of a blocked nose, loss of smell, and dry, greenish crusty nasal discharge. On examination, nasal cavities appear roomy, and turbinates are atrophied. Which of the following surgeries can be done on this patient?", "options": [{"label": "A", "text": "Submucous Resection", "correct": false}, {"label": "B", "text": "Young's Operation", "correct": true}, {"label": "C", "text": "Septoplasty", "correct": false}, {"label": "D", "text": "Vidian Neurectomy", "correct": false}], "correct_answer": "B. Young's Operation", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Young's Operation The given clinical scenario is suggestive of atrophic rhinitis . Young's and modified Young's operations are done to treat atrophic rhinitis. In Young’s operation - Both the nostrils are closed entirely just within the nasal vestibule by raising flaps. They are opened after six months or later. In these cases, the mucosa may revert to normal and crusting reduced.</p>\n<p><strong>Highyeild:</strong></p><p>ATROPHIC RHINITIS/OZAENA The most common organism causing primary atrophic rhinitis is Klebsiella ozaena. It is a chronic nose inflammation characterized by atrophy of nasal mucosa and turbinate bones. The nasal cavities are roomy and full of foul-smelling crusts. The disease is commonly seen in females and starts around puberty. The nose has a foul smell, making the patient a social outcast. However, the patient is unaware of the scent due to marked anosmia ( merciful anosmia ), which accompanies these degenerative changes.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A. Submucous Resection is done in deviated nasal septum. Option: C. Septoplasty is also done in deviated nasal septum. Option: D. Vidian Neurectomy is done in vasomotor rhinitis for excessive rhinorrhea.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient came to your hospital and was diagnosed with rhinitis medicamentosa. Prolonged use of which of the following drugs can cause rhinitis medicamentosa?", "options": [{"label": "A", "text": "Steroid nasal spray", "correct": false}, {"label": "B", "text": "Antihistamines", "correct": false}, {"label": "C", "text": "Leukotriene receptor antagonists", "correct": false}, {"label": "D", "text": "Nasal Decongestants", "correct": true}], "correct_answer": "D. Nasal Decongestants", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Nasal Decongestants Rhinitis medicamentosa is caused by the prolonged use of nasal decongestants. The treatment includes the withdrawal of nasal decongestant drops, a short course of systemic steroid therapy, and in some cases, surgical reduction of turbinates if they have become hypertrophied.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 33 years old woman with nasal discharge and crusting is diagnosed with ozaena. Which of the following disease is also called ozaena?", "options": [{"label": "A", "text": "Hypertrophic Rhinitis", "correct": false}, {"label": "B", "text": "Vasomotor Rhinitis", "correct": false}, {"label": "C", "text": "Atrophic Rhinitis", "correct": true}, {"label": "D", "text": "Allergic Rhinitis", "correct": false}], "correct_answer": "C. Atrophic Rhinitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Atrophic Rhinitis Atrophic Rhinitis is also called ozaena.</p>\n<p><strong>Highyeild:</strong></p><p>ATROPHIC RHINITIS/OZAENA The most common organism causing primary atrophic rhinitis is Klebsiella ozaena. It is a chronic nose inflammation characterized by atrophy of nasal mucosa and turbinate bones. The nasal cavities are roomy and full of foul-smelling crusts. The disease is commonly seen in females and starts around puberty. The nose has a foul smell, making the patient a social outcast. However, the patient is unaware of the scent due to marked anosmia ( merciful anosmia ) accompanying these degenerative changes.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A. In Hypertrophic Rhinitis, Examination shows hypertrophy of turbinates. Turbinal mucosa is thick and does not pit on pressure. Option: B. In Vasomotor Rhinitis, Nasal mucosa over the turbinates is generally congested and hypertrophic. Option: D. In atrophic rhinitis, cardinal symptoms of seasonal nasal allergy include paroxysmal sneezing, 10–20 sneezes at a time, nasal obstruction, watery nasal discharge, and itching in the nose.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Patient presented to the ENT department with a history of marked anosmia .her parents complained of a foul smell coming from her nose all the time. On examination, her nasal cavity was roomy and filled with a grey-black dry crust. Which of the following is not a part of the mixture used to dissolve and remove the crust of the nose in case of atrophic rhinitis?", "options": [{"label": "A", "text": "Sodium Chloride", "correct": false}, {"label": "B", "text": "Sodium Bicarbonate", "correct": false}, {"label": "C", "text": "Sodium Bi Borate", "correct": false}, {"label": "D", "text": "Sodium Hydroxide", "correct": true}], "correct_answer": "D. Sodium Hydroxide", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Sodium Hydroxide Nasal irrigation and removal of The crust in case of atrophic rhinitis is done by the use of warm normal saline for an alkaline solution made by dissolving 1 part of sodium bicarbonate One piece of sodium by borate Two parts of sodium chloride in 80 ml of water are used to irrigate the nasal cavity.</p>\n<p><strong>Highyeild:</strong></p><p>ATROPHIC RHINITIS/OZAENA It is a chronic nose inflammation characterized by atrophy of nasal mucosa and turbinate bones. The nasal cavities are roomy and full of foul-smelling crusts. The disease is commonly seen in females and starts around puberty. The nose has a foul smell, making the patient a social outcast. However, the patient is unaware of the scent due to marked anosmia ( merciful anosmia ) accompanying these degenerative changes. Patients may complain of nasal obstruction despite unduly vast nasal chambers. This is due to large crusts filling the nose. Epistaxis may occur when the crusts are removed.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Periodicity is a characteristic feature of which sinus infection:", "options": [{"label": "A", "text": "Maxillary sinus infection", "correct": false}, {"label": "B", "text": "Frontal sinus infection", "correct": true}, {"label": "C", "text": "Sphenoid sinus infection", "correct": false}, {"label": "D", "text": "Ethmoid sinus infection", "correct": false}], "correct_answer": "B. Frontal sinus infection", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Frontal sinus infection The pain of frontal sinusitis shows characteristic periodicity, e., it comes upon waking, gradually increases and reaches its peak by midday, and then subsides. It is also called “office headache” as it is present only during office hours.</p>\n<p><strong>Highyeild:</strong></p><p>CLINICAL FEATURES OF FRONTAL SINUSITIS Frontal headache. Usually severe and localized over the affected sinus. It shows characteristic periodicity,i.e., it comes up on waking, gradually increases, reaches its peak by about mid-day, and then subsides. It is also called an “office headache” because of its presence only during office hours. Pressure upwards on the frontal sinus floor, just above the medial canthus, causes exquisite pain. It can also be elicited by tapping over the frontal sinus's anterior wall in the supraorbital region's medial part. Oedema of the upper eyelid with suffused conjunctiva and photophobia. Nasal discharge. A vertical streak of mucopus is seen high up in the anterior part of the middle meatus. This may be absent if the ostium is closed with no drainage. The nasal mucosa is inflamed in the middle meatus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A middle-aged man comes to the hospital complaining of secretion from the nasal cavity. On examination, caseous material is seen in the Nose. He was diagnosed to have rhinitis cases. Which of the following is false about rhinitis cases?", "options": [{"label": "A", "text": "Associated with suppurative sinusitis", "correct": false}, {"label": "B", "text": "more typical in males", "correct": false}, {"label": "C", "text": "It is a bilateral condition", "correct": true}, {"label": "D", "text": "Also known as nasal cholesteatoma", "correct": false}], "correct_answer": "C. It is a bilateral condition", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>It is a bilateral condition Rhinitis caseosa is a unilateral condition and not bilateral.</p>\n<p><strong>Highyeild:</strong></p><p>RHINITIS CASEOSA It is an uncommon condition, usually unilateral, and mainly affects The nose is filled with offensive, purulent discharge, and cheesy material. The disease possibly arises from chronic sinusitis with a collection of inspissated cheesy material. Sinus mucosa becomes granulomatous. Bony walls of the sinus may be destroyed, requiring differentiation from malignancy. Treatment is removing debris and granulation tissue and free drainage of the affected sinus. The prognosis is good.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A. There will be an accumulation of secretions in the nasal cavity, which undergoes putrefaction resulting in the formation of caseous material. It is usually associated with suppurative sinusitis. Option: B. Rhinitis caseosa is more common in males is a true statement. Option: D. It is also known as nasal cholesteatoma.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 22 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 22</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Anatomy Of External Ear & Tympanic Membrane - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 22</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 22 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Pinna is not supplied by?", "options": [{"label": "A", "text": "Facial Nerve", "correct": false}, {"label": "B", "text": "Vagus Nerve", "correct": false}, {"label": "C", "text": "Greater Occipital Nerve", "correct": true}, {"label": "D", "text": "Auriculotemporal Nerve", "correct": false}], "correct_answer": "C. Greater Occipital Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Greater Occipital Nerve Pinna is not supplied by the greater occipital nerve.</p>\n<p><strong>Highyeild:</strong></p><p>NERVE SUPPLY OF PINNA Greater auricular nerve (C2,3) supplies most of the medial surface of the pinna and only the posterior part of the lateral surface Lesser occipital (C2) supplies the upper part of the medial surface. Auriculotemporal (V3) supplies the tragus, the crus of the helix, and the adjacent part of the helix. The auricular branch of the vagus (CN X), also called Arnold’s nerve, supplies the concha and corresponding eminence on the medial surface. The facial nerve, which is distributed with fibers of the auricular branch of the vagus, supplies the concha and retro auricular groove.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Facial nerve, which is distributed with fibers of the auricular branch of the vagus, supplies the concha and retro auricular groove. Option: B. Auricular branch of the vagus (CN X), also called Arnold’s nerve, supplies the concha and corresponding eminence on the medial surface. Option: D. Auriculotemporal (V3) supplies tragus, the crus of the helix, and the adjacent part of the helix.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are true about myringitis bullosa except:", "options": [{"label": "A", "text": "It is a painless condition", "correct": true}, {"label": "B", "text": "Characterized by the formation of hemorrhagic blebs on the tympanic membrane", "correct": false}, {"label": "C", "text": "Caused by virus or mycoplasma pneumonia", "correct": false}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "A. It is a painless condition", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>It is a painless condition Myringitis bullosa is a painful condition characterized by the formation of hemorrhagic blebs on the tympanic membrane and deep meatus. It is probably caused by a virus or Mycoplasma pneumoniae .</p>\n<p><strong>Highyeild:</strong></p><p>M YRINGITIS G RANULOSA . Nonspecific granulations form on the outer surface of the tympanic membrane. It may be associated with impacted wax, long-standing foreign body, or external ear infection.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Myringitis bullosa is Characterized by the formation of hemorrhagic blebs on the tympanic membrane Option: C. Myringitis bullosa is probably caused by a virus or Mycoplasma pneumoniae .</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following can cause tympanic membrane rupture?", "options": [{"label": "A", "text": "Trauma due to hairpin, matchstick", "correct": false}, {"label": "B", "text": "Sudden changes in air pressure like a slap", "correct": false}, {"label": "C", "text": "Pressure by fluid column example diving", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above All of the above can cause a rupture of the tympanum membrane</p>\n<p><strong>Highyeild:</strong></p><p>Tympanic membrane may be ruptured by- Trauma due to a hairpin, matchstick, or unskilled attempts to remove a foreign body. Sudden change in air pressure, e.g. a slap or a kiss on the ear or a sudden blast. Forceful Valsalva may rupture a thin atrophic membrane. Pressure by a fluid column, e.g. diving, water sports, or forceful syringing. Fracture of the temporal bone .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Trauma due to a hairpin, or matchstick can cause tympanic membrane rupture. Option: B. Sudden change in air pressure, e.g. a slap or a kiss on the ear or a sudden blast can cause tympanic membrane rupture. Option: C. Pressure by a fluid column, e.g. diving, water sports, or forceful syringing can cause tympanic membrane rupture.</p>\n<p><strong>Extraedge:</strong></p><p>The tympanic membrane consists of three layers: Outer epithelial layer, which is continuous with the skin lining the meatus. Inner mucosal layer, which is continuous with the mucosa of the middle ear. Middle fibrous layer, which encloses the handle of the malleus and has three types of fibers—the radial, circular, and parabolic.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following statements is correct regarding the healed tympanic membrane?", "options": [{"label": "A", "text": "Has all the three layers", "correct": false}, {"label": "B", "text": "Has only an epithelial layer", "correct": false}, {"label": "C", "text": "Has outer epithelial and inner mucosal layers only", "correct": true}, {"label": "D", "text": "Has fibrous layer only", "correct": false}], "correct_answer": "C. Has outer epithelial and inner mucosal layers only", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Has outer epithelial and inner mucosal layers only Perforation of the tympanic membrane heals only by epithelial and mucosal layers without the intervening fibrous layer.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Healed tympanic membrane doesn’t have all 3 layers. Option: B. Perforation of tympanic membrane heals by epithelial and mucosal layers Option: D. Perforation of tympanic membrane heals by epithelial and mucosal layers</p>\n<p><strong>Extraedge:</strong></p><p>The tympanic membrane consists of three layers: Outer epithelial layer, which is continuous with the skin lining the meatus. Inner mucosal layer, which is continuous with the mucosa of the middle ear. Middle fibrous layer, which encloses the handle of the malleus and has three types of fibers—the radial, circular, and parabolic.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All of the following nerves supply Auricle and external auditory canal except?", "options": [{"label": "A", "text": "Trigeminal Nerve", "correct": false}, {"label": "B", "text": "Glossopharyngeal Nerve", "correct": true}, {"label": "C", "text": "Facial Nerve", "correct": false}, {"label": "D", "text": "Vagus Nerve", "correct": false}], "correct_answer": "B. Glossopharyngeal Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Glossopharyngeal Nerve Glossopharyngeal Nerve doesn’t supply the auricle and external auditory canal.</p>\n<p><strong>Highyeild:</strong></p><p>NERVE SUPPLY OF EXTERNAL AUDITORY CANAL Anterior wall and roof - auriculotemporal (V3). Posterior wall and floor - an auricular branch of the vagus (CN X). The posterior wall of the auditory canal also receives sensory fibers of CN VII through an auricular branch of the vagus</p>\n<p><strong>Extraedge:</strong></p><p>NERVE SUPPLY OF PINNA Greater auricular nerve (C2,3) supplies most of the medial surface of the pinna and only the posterior part of the lateral surface Lesser occipital (C2) supplies the upper part of the medial surface. Auriculotemporal (V3) supplies the tragus, crus of the helix, and the adjacent part of the helix. The auricular branch of the vagus (CN X), also called Arnold’s nerve, supplies the concha and corresponding eminence on the medial surface. The facial nerve, which is distributed with fibers of the auricular branch of the vagus, supplies the concha and retro auricular groove.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Ceruminous glands present in the ear are:", "options": [{"label": "A", "text": "Modified Eccrine Glands", "correct": false}, {"label": "B", "text": "Modified Apocrine Glands", "correct": true}, {"label": "C", "text": "Mucous Gland", "correct": false}, {"label": "D", "text": "Modified Holocrine Glands", "correct": false}], "correct_answer": "B. Modified Apocrine Glands", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Modified Apocrine Glands Ceruminous glands of the external acoustic meatus and ciliary glands of eyelids are modified apocrine glands.</p>\n<p><strong>Highyeild:</strong></p><p>Sebaceous and ceruminous (modified sweat glands) glands open into the space of the hair follicle. Sebaceous glands provide fluid rich in fatty acids while secretion of ceruminous glands is rich in lipids and pigment granules. Secretion of both these glands mixes with the desquamated epithelial cells and keratin shed from the tympanic membrane and deep bony meatus to form wax.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Ceruminous glands and pilosebaceous units which secrete wax are situated in?", "options": [{"label": "A", "text": "Only in the outer cartilaginous portion external auditory canal", "correct": true}, {"label": "B", "text": "Only in the bony part of the external auditory canal", "correct": false}, {"label": "C", "text": "Both in the cartilaginous and bony parts of the external auditory canal", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "A. Only in the outer cartilaginous portion external auditory canal", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Only in the outer cartilaginous portion external auditory canal Hair is only confined to the outer canal, and therefore , furuncles (staphylococcal infection of hair follicles) are seen only in the outer one-third of the canal. This is the reason why furuncle is present only in the cartilaginous part of the external auditory canal where hair follicles are present</p>\n<p><strong>Highyeild:</strong></p><p>EXTERNAL AUDITORY CANAL The canal is divided into two parts: Cartilaginous Part It forms the outer one-third (8 mm) of the canal. Cartilage is a continuation of the cartilage which forms the framework of the pinna. Bony Part It forms the inner two-thirds (16 mm). The skin lining the bony canal is thin and continuous over the tympanic membrane. It is devoid of hair and ceruminous glands. About 6 mm lateral to the tympanic membrane, the bony meatus presents a narrowing called the isthmus.</p>\n<p><strong>Extraedge:</strong></p><p>NERVE SUPPLY OF EXTERNAL AUDITORY CANAL Anterior wall and roof - auriculotemporal (V3). Posterior wall and floor - an auricular branch of the vagus (CN X). The posterior wall of the auditory canal also receives sensory fibers of CN VII through an auricular branch of the vagus</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The medial surface of the tympanic membrane is supplied by-", "options": [{"label": "A", "text": "Auricular branch of vagus", "correct": false}, {"label": "B", "text": "Auriculotemporal Nerve", "correct": false}, {"label": "C", "text": "Jacobson's Nerve", "correct": true}, {"label": "D", "text": "Facial Nerve", "correct": false}], "correct_answer": "C. Jacobson's Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Jacobson's Nerve Jacobson’s nerve is a branch of the glossopharyngeal nerve which supplies the medial surface of the tympanic membrane.</p>\n<p><strong>Highyeild:</strong></p><p>NERVE SUPPLY OF TYMPANIC MEMBRANE Anterior half of lateral surface: auriculotemporal (V3). Posterior half of lateral surface: auricular branch of vagus (CN X). Medial surface: tympanic branch of CN IX (Jacobson’s nerve).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Auricular branch of the vagus (CN X), also called Arnold’s nerve, supplies the concha and corresponding eminence on the medial surface. Option: B. Auriculotemporal (V3) supplies tragus, the crus of the helix, and the adjacent part of the helix. Option: D. Facial nerve, which is distributed with fibers of the auricular branch of the vagus, supplies the concha and retro auricular groove.</p>\n<p><strong>Extraedge:</strong></p><p>NERVE SUPPLY OF EXTERNAL AUDITORY CANAL Anterior wall and roof - auriculotemporal (V3). Posterior wall and floor - an auricular branch of the vagus (CN X). The posterior wall of the auditory canal also receives sensory fibers of CN VII through an auricular branch of the vagus</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Pinna has which type of cartilage:", "options": [{"label": "A", "text": "Yellow Elastic Cartilage", "correct": true}, {"label": "B", "text": "Hyaline Cartilage", "correct": false}, {"label": "C", "text": "Fibrocartilage", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "A. Yellow Elastic Cartilage", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Yellow Elastic Cartilage The auricle except its lobule is made up of a framework of a single piece of yellow elastic cartilage which is covered with skin.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Thyroid, cricoid, and most of the arytenoid cartilages are hyaline cartilages. Option: C. Medial part of the eustachian tube is made up of fibrocartilage.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A surgeon doctor 'X' wants to do surgery in order to approach the external auditory canal. He wants to follow an endaural approach. The area where he should make an incision is:", "options": [{"label": "A", "text": "Over The Helix", "correct": false}, {"label": "B", "text": "Over Incisura Terminalis", "correct": true}, {"label": "C", "text": "On Tragus", "correct": false}, {"label": "D", "text": "Any of the above", "correct": false}], "correct_answer": "B. Over Incisura Terminalis", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996261838-QTDE012011IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Over Incisura Terminalis Over incisura terminalis, as there is no cartilage present here hence incision made in this area will not cut through the cartilage and is used for endaural approach in surgeries of the ear. This decreases the risk of perichondritis of the cartilage of the pinna after surgery .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Over The Helix cartilage is present so the risk of perichondritis is there; hence incision is avoided here. Option: C. On Tragus also cartilage is present so the risk of perichondritis is there; hence incision is avoided here.</p>\n<p><strong>Extraedge:</strong></p><p>Lempert II incision Starts from the first incision at 12 o’clock and then passes upwards in a curvilinear fashion between the tragus and the crus of the helix. It passes through the incisura terminals and thus does not cut the cartilage. Both mastoid and external canal surgery can be done.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The inner part of the external auditory canal is directed in which direction:", "options": [{"label": "A", "text": "Upwards Backwards Medially", "correct": false}, {"label": "B", "text": "Upward backward laterally", "correct": false}, {"label": "C", "text": "Downward Forward Medially", "correct": true}, {"label": "D", "text": "Backward Forward Laterally", "correct": false}], "correct_answer": "C. Downward Forward Medially", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Downward Forward Medially The inner part of the external auditory canal is directed downwards, forward, and medially. the outer part is directed upwards, backward, and medially.</p>\n<p><strong>Highyeild:</strong></p><p>EXTERNAL AUDITORY CANAL It extends from the bottom of the concha to the tympanic membrane and measures about 24 mm along its posterior wall. It is not a straight tube. The inner part of the external auditory canal is directed downwards, forward, and media the ly. the outer part is directed upwards, backward, and medially. Therefore, to see the tympanic membrane, the pinna has to be pulled upwards, backward, and laterally so as to bring the two parts in alignment.</p>\n<p><strong>Extraedge:</strong></p><p>The canal is divided into two parts: Cartilaginous Part It forms the outer one-third (8 mm) of the canal. Cartilage is a continuation of the cartilage which forms the framework of the pinna. Bony Part] It forms the inner two-thirds (16 mm). The skin lining the bony canal is thin and continuous over the tympanic membrane. It is devoid of hair and ceruminous glands. About 6 mm lateral to the tympanic membrane, the bony meatus presents a narrowing called the isthmus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "To see the tympanic membrane the pinna has to be pulled:", "options": [{"label": "A", "text": "Upwards backward and laterally", "correct": true}, {"label": "B", "text": "Upward backward and medially", "correct": false}, {"label": "C", "text": "Downward forward and medially", "correct": false}, {"label": "D", "text": "Backward forward and laterally", "correct": false}], "correct_answer": "A. Upwards backward and laterally", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Upwards backward and laterally To see the tympanic membrane the pinna has to be pulled upward backward and laterally to bring the two lateral and middle parts of the external auditory canal in a single alignment</p>\n<p><strong>Extraedge:</strong></p><p>The canal is divided into two parts: Cartilaginous Part It forms the outer one-third (8 mm) of the canal. Cartilage is a continuation of the cartilage which forms the framework of the pinna. Bony Part] It forms the inner two-thirds (16 mm). The skin lining the bony canal is thin and continuous over the tympanic membrane. It is devoid of hair and ceruminous glands. About 6 mm lateral to the tympanic membrane, the bony meatus presents a narrowing called the isthmus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An 18 yrs. old patient presents with earache, hearing loss, and tinnitus. On examination, the ear canal is full of pearly white material disposed of in several layers. What is the management of likely diagnosis?", "options": [{"label": "A", "text": "Anti-Fungal", "correct": false}, {"label": "B", "text": "Keratolytic Agents", "correct": false}, {"label": "C", "text": "Antibiotics", "correct": false}, {"label": "D", "text": "Syringing", "correct": true}], "correct_answer": "D. Syringing", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Syringing The probable diagnosis is Keratosis obturans . The ear canal shows a pearly white mass of keratin material disposed of in several layers. Keratotic mass is removed by syringing or instrumentation.</p>\n<p><strong>Highyeild:</strong></p><p>KERATOSIS OBTURANS The collection of a pearly white mass of desquamated epithelial cells in the deep meatus is called keratosis obturates. By its pressure effect, it causes absorption of bone leading to the widening of the meatus so much so that the facial nerve may be exposed and paralyzed. Commonly seen between 5 and 20 years and may affect one or both ears. Normally, epithelium from the surface of the tympanic membrane migrates onto the posterior meatal wall. Failure of this migration or obstruction to migration caused by wax may lead to accumulation of the epithelial plug in the deep meatus. Presenting symptoms may be a pain in the ear, hearing loss, tinnitus, and sometimes ear discharge.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Antifungal is not recommended because keratosis obturates are a collection of desquamated epithelial cells in the deep meatus. Option: B. Keratolytic agents [ 2% salicylic acid ] are recommended for recurrence. Option: C. Antibiotics are not recommended because keratosis obturates are a collection of desquamated epithelial cells in the deep meatus.</p>\n<p><strong>Extraedge:</strong></p><p>(A) Irrigation of the ear canal. (B) Illustration to show how a jet of water expels wax or a foreign body.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The external auditory canal develops from the first branchial cleft and it is ectoderm in nature. Which of the following statement is true for the external auditory canal:", "options": [{"label": "A", "text": "Outer 1/3rd is cartilaginous and the inner 2/3rd is the bony part", "correct": true}, {"label": "B", "text": "Outer 2/3rd is cartilaginous and inner 1/3rd is the bony part", "correct": false}, {"label": "C", "text": "Outer 1/3rd is bony and inner 2/3rd is cartilaginous part", "correct": false}, {"label": "D", "text": "Outer 2/3rd is bony and inner 1/3rd is cartilaginous part", "correct": false}], "correct_answer": "A. Outer 1/3rd is cartilaginous and the inner 2/3rd is the bony part", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Outer 1/3rd is cartilaginous and the inner 2/3rd is the bony part The cartilaginous part of the external auditory canal forms the outer one-third (8 mm) of the canal and the bony part forms the inner two-thirds (16 mm).</p>\n<p><strong>Highyeild:</strong></p><p>EXTERNAL AUDITORY CANAL The canal is divided into two parts: Cartilaginous Part It forms the outer one-third (8 mm) of the canal. Cartilage is a continuation of the cartilage which forms the framework of the pinna. Bony Part It forms the inner two-thirds (16 mm). The skin lining the bony canal is thin and continuous over the tympanic membrane. It is devoid of hair and ceruminous glands. About 6 mm lateral to the tympanic membrane, the bony meatus presents a narrowing called the isthmus.</p>\n<p><strong>Extraedge:</strong></p><p>EXTERNAL AUDITORY CANAL It extends from the bottom of the concha to the tympanic membrane and measures about 24 mm along its posterior wall. It is not a straight tube. The inner part of the external auditory canal is directed downwards, forward, and medially. the outer part is directed upwards, backward, and medially. Therefore, to see the tympanic membrane, the pinna has to be pulled upwards, backward, and laterally so as to bring the two parts in alignment.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In a patient with conductive hearing loss external auditory Canal is filled with wax. Wax is composed of epithelium debris along with the secretion of the sebaceous gland and ceruminous gland. Ceruminous glands are which type of glands?", "options": [{"label": "A", "text": "Modified Eccrine glands", "correct": false}, {"label": "B", "text": "Modified apocrine glands", "correct": true}, {"label": "C", "text": "Modified Holocrine glands", "correct": false}, {"label": "D", "text": "Modified endocrine glands", "correct": false}], "correct_answer": "B. Modified apocrine glands", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Modified apocrine glands Ceruminous glands are modified apocrine sweat glands . They open into hair follicles & participate in wax production.</p>\n<p><strong>Highyeild:</strong></p><p>APOCRINE GLANDS Apocrine glands release their products by decapitation. It is a process by which membrane-bound cytoplasm from the apical surface of the cells buds off into the lumen of the duct and is secreted. Some apocrine glands have specific names those on the eyelids are referred to as Moll's glands, and those on the external auditory meatus are termed ceruminous glands. Sebaceous and ceruminous (modified sweat glands) glands open into the space of the hair follicle.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Options A.,B.,D. : Ethosuximide, Gabapentin, Lorazepam do not cause Steven Johnson syndrome.</p>\n<p><strong>Extraedge:</strong></p><p>Sebaceous glands provide fluid rich in fatty acids while secretion of ceruminous gland is rich in lipids and pigment granules. Secretion of both these glands mixes with the desquamated epithelial cells and keratin shed from the tympanic membrane and deep bony meatus to form wax.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient with conductive hearing loss comes to the clinic while performing the ear examination cone of light is seen in which quadrant of the tympanic membrane:", "options": [{"label": "A", "text": "Antero-Superior", "correct": false}, {"label": "B", "text": "Postero-Superior", "correct": false}, {"label": "C", "text": "Antero-Inferior", "correct": true}, {"label": "D", "text": "Postero-Inferior", "correct": false}], "correct_answer": "C. Antero-Inferior", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Antero-Inferior A bright cone of light can be seen radiating from the tip of the malleus (umbo) to the periphery in the anteroinferior quadrant of the tympanic membrane.</p>\n<p><strong>Highyeild:</strong></p><p>'</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In a patient with a glue ear while performing tympanometry compliance of the tympanic membrane is decreased. All of the following options are true about the tympanic membrane except", "options": [{"label": "A", "text": "It has 3 layers", "correct": false}, {"label": "B", "text": "It is derived from all 3 germ layers", "correct": false}, {"label": "C", "text": "It separates External from the middle ear", "correct": false}, {"label": "D", "text": "Central part is more mobile", "correct": true}], "correct_answer": "D. Central part is more mobile", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Central part is more mobile Movements of the tympanic membrane are more at the periphery than at the center where the handle of the malleus is attached.</p>\n<p><strong>Highyeild:</strong></p><p>TYMPANIC MEMBRANE Tympanic membrane has 3 layers -outer epithelial, middle fibrous, and inner mucosal layer: It develops from all three germinal layers. The outer epithelial layer is formed by the ectoderm, the inner mucosal layer by the endoderm, and the middle fibrous layer by the mesoderm. Movements of the tympanic membrane are more at the periphery than at the center where the handle of the malleus is attached. A bright cone of light can be seen radiating from the tip of the malleus (umbo) to the periphery in the anteroinferior quadrant of the tympanic membrane.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. It has 3 layers is a true statement. Option: B. It is derived from all 3 germ layers is also a true statement. Option: C. It separates the External from the middle ear is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is true about the external auditory canal?", "options": [{"label": "A", "text": "Outer 2/3rd is cartilaginous and the inner 1/3rd is the bony part", "correct": false}, {"label": "B", "text": "Furuncles are seen in the cartilaginous part", "correct": true}, {"label": "C", "text": "Fissures of Santorini are present in the bony part and permit parotid and superficial mastoid infections to spread to the canal", "correct": false}, {"label": "D", "text": "Isthmus is present at the junction of the cartilaginous and fibrous part.", "correct": false}], "correct_answer": "B. Furuncles are seen in the cartilaginous part", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Furuncles are seen in the cartilaginous part Hair is only confined to the outer canal, and therefore , furuncles (staphylococcal infection of hair follicles) are seen only in the outer one-third of the canal. Skin is also very tightly adherent to the cartilage and hence furuncles are extremely painful.</p>\n<p><strong>Highyeild:</strong></p><p>EXTERNAL AUDITORY CANAL The canal is divided into two parts: Cartilaginous Part It forms the outer one-third (8 mm) of the canal. Cartilage is a continuation of the cartilage which forms the framework of the pinna. Bony Part It forms the inner two-thirds (16 mm). The skin lining the bony canal is thin and continuous over the tympanic membrane. It is devoid of hair and ceruminous glands. About 6 mm lateral to the tympanic membrane, the bony meatus presents a narrowing called the isthmus.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Cartilaginous part is the outer one-third of the canal and the inner two third is bony. Option: C. Fissures of Santorini are present in the cartilaginous part and permit parotid and superficial mastoid infections to spread to the canal Option: D. About 6 mm lateral to the tympanic membrane, the bony meatus presents a narrowing called isthmus; not at the bone- cartilaginous junction.</p>\n<p><strong>Extraedge:</strong></p><p>EXTERNAL AUDITORY CANAL It extends from the bottom of the concha to the tympanic membrane and measures about 24 mm along its posterior wall. It is not a straight tube. The inner part of the external auditory canal is directed downwards, forward, and medially. the outer part is directed upwards, backward, and medially. Therefore, to see the tympanic membrane, the pinna has to be pulled upwards, backward, and laterally so as to bring the two parts in alignment.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 3-year-old child has come to your clinic with a complaint of discharge from ear. You examine the ear during the examination of the ear, cough response is elicited due to stimulation of which nerve?", "options": [{"label": "A", "text": "Facial Nerve", "correct": false}, {"label": "B", "text": "Vagus Nerve", "correct": true}, {"label": "C", "text": "Trigeminal Nerve", "correct": false}, {"label": "D", "text": "Branches of C1, C2", "correct": false}], "correct_answer": "B. Vagus Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Vagus Nerve Reflex cough due to stimulation of the auricular branch of the vagus which supplies part of the EAC may sometimes occur during the examination of the ear as it also supplies the larynx.</p>\n<p><strong>Highyeild:</strong></p><p>Nerve supply of External auditory canal Anterior wall and roof- an auriculotemporal branch of the mandibular division of the trigeminal nerve (CN V3). Posterior wall and floor - an auricular branch of the Vagus (Arnold's nerve). Cough response while cleaning the ear canal is mediated by the vagus which also supplies the larynx. The posterior wall also receives sensory supply from the facial nerve (CN VII).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient with parotid swelling gives a history of external auditory canal infection 7 days back. You suspect the route of parotid swelling is the fissure of Santorini. Which of the following options is true about the Fissure of Santorini?", "options": [{"label": "A", "text": "Deficiency in the cartilaginous canal", "correct": true}, {"label": "B", "text": "Connection between Middle ear and antrum", "correct": false}, {"label": "C", "text": "Connection between Inner ear and middle ear", "correct": false}, {"label": "D", "text": "Deficiency in the bony canal", "correct": false}], "correct_answer": "A. Deficiency in the cartilaginous canal", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Deficiency in the cartilaginous canal The cartilaginous part of the EAC has two deficiencies called the \"fissures of Santorini\".</p>\n<p><strong>Highyeild:</strong></p><p>The cartilaginous part of the EAC has two deficiencies called the \"fissures of Santorini\". They provide a pathway for the spread of parotid or superficial mastoid infections to the canal or vice versa. The bony canal also has a deficiency called \"foramen of Huschke\" which also acts as a pathway for the spread of infections and tumors to the deep lobe of the parotid. EXTERNAL AUDITORY CANAL The canal is divided into two parts: Cartilaginous Part It forms the outer one-third (8 mm) of the canal. Cartilage is a continuation of the cartilage which forms the framework of the pinna. Bony Part It forms the inner two-thirds (16 mm). The skin lining the bony canal is thin and continuous over the tympanic membrane. It is devoid of hair and ceruminous glands. About 6 mm lateral to the tympanic membrane, the bony meatus presents a narrowing called the isthmus.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. The connection between the Middle ear and the antrum is a false statement; instead, they provide a pathway for the spread of parotid or superficial mastoid infections to the canal or vice versa. Option: C. The connection between the Inner ear and the middle ear is a false statement; instead, they provide a pathway for the spread of parotid or superficial mastoid infections to the canal or vice versa. Option: D. Deficiency in the bony canal is a false statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "What can be used for reconstructive surgery of the middle ear?", "options": [{"label": "A", "text": "Cartilage From Tragus", "correct": false}, {"label": "B", "text": "Perichondrium from Tragus or Chonca", "correct": false}, {"label": "C", "text": "Fat From Lobule", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above Cartilage from the tragus, perichondrium from the tragus or concha, and fat from the lobule are frequently used for reconstructive surgery of the middle ear . The conchal cartilage has also been used to correct the depressed nasal bridge while the composite grafts of the skin and cartilage from the pinna are sometimes used for repair of defects of nasal ala .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Cartilage From Tragus is a true statement. Option: B. Perichondrium from tragus or chonca is a true statement. Option: C. Fat From Lobule is a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In a normal ear, which of the following is/are true statements? A. The total length of the external ear canal is 36 mm B. Pinna has to be pulled upwards and backward to see the tympanic membrane C. External ear canal does not contain ceruminous glands or hair follicles in the deep bony part D. Dehiscences may be seen in the outer cartilaginous canal Select the correct answer from the given below code:", "options": [{"label": "A", "text": "B, C & D Are True", "correct": true}, {"label": "B", "text": "C & D Are True", "correct": false}, {"label": "C", "text": "Only D is True", "correct": false}, {"label": "D", "text": "All Are True", "correct": false}], "correct_answer": "A. B, C & D Are True", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>B, C & D Are True Pinna has to be pulled upwards and backward to see the tympanic membrane The external ear canal does not contain ceruminous glands or hair follicle s in the deep bony part Dehiscences may be seen in the outer cartilaginous canal The total length of the external auditory canal from the concha to the tympanic membrane is 24 mm (compare eustachian tube which is 36 mm). Its outer one-third is cartilaginous while its inner two-thirds are bony.</p>\n<p><strong>Highyeild:</strong></p><p>Total length of the external auditory canal from concha to tympanic membrane is 24 mm (compare eustachian tube which is 36 mm). Its outer one-third is cartilaginous while inner two-third are bony. Hair follicles, sebaceous and ceruminous glands are confined only to its outer one-third. Boil, a staphylococcal infection of the hair follicle, therefore occurs only in the outer part of the canal. Floor of the cartilage forming the outer cartilaginous canal may show dehiscences called fissures of Santorini, which permit infections of the parotid or from the mastoid to present in the canal and vice versa.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 32 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 10</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Anatomy Of Larynx (Part 1) - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 10</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 10 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "All Are Techniques Done In Type 1 Thyroplasty Except", "options": [{"label": "A", "text": "Transoral vocal cord injection", "correct": false}, {"label": "B", "text": "Window over the thyroid cartilage", "correct": false}, {"label": "C", "text": "Arytenoids Adduction Technique", "correct": false}, {"label": "D", "text": "Anterior Commissure Advancement", "correct": true}], "correct_answer": "D. Anterior Commissure Advancement", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Anterior Commissure Advancement Anterior commissure advancement is not done in type I thyroplasty.</p>\n<p><strong>Highyeild:</strong></p><p>In Thyroplasty type I Vocal cord is medialized towards the midline for the opposite cord to meet. This can be combined with the arytenoid adduction procedure . Thyroplasty is done by creating a window in the thyroid cartilage and placing a silicon or other prosthesis to medialize the cord. The operation can be done under local anaesthesia.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Transoral vocal cord teflo injection is given to medialize the cord. Option: B . window over the thyroid cartilage is created in type I thyroplasty. Option: C . arytenoid adduction procedure may be combined with vocal cord medialization in type I thyroplasty.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 30-year female patient underwent total thyroidectomy. After surgery patients complain of inability to raise the pitch of their voice the most likely cause of it is an injury to", "options": [{"label": "A", "text": "The internal branch of the superior laryngeal nerve", "correct": false}, {"label": "B", "text": "External branch of superior laryngeal nerve", "correct": true}, {"label": "C", "text": "Recurrent Laryngeal Nerve", "correct": false}, {"label": "D", "text": "Ansa cervicalis", "correct": false}], "correct_answer": "B. External branch of superior laryngeal nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>External branch of superior laryngeal nerve The question mentions that it is after total thyroidectomy and also that there is the inability to raise the pitch of voice due to the involvement of cricothyroid. This happens in injury to the External branch of the superior laryngeal nerve.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. internal branch of superior laryngeal nerve provides a sensory supply of larynx above the vocal cords. Option: C. Recurrent laryngeal nerve paralysis results in median/ paramedian position of vocal cords. Option: D . ansa cervicalis supplies infrahyoid muscles.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The sensory nerve supply of the larynx below the level of the vocal cord is", "options": [{"label": "A", "text": "External branch of superior laryngeal nerve", "correct": false}, {"label": "B", "text": "The internal branch of the superior laryngeal nerve", "correct": false}, {"label": "C", "text": "Recurrent laryngeal nerve", "correct": true}, {"label": "D", "text": "Inferior Pharyngeal", "correct": false}], "correct_answer": "C. Recurrent laryngeal nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Recurrent laryngeal nerve Sensory Nerve supply of the larynx Above the vocal cords, the larynx is supplied by the internal laryngeal nerve —a branch of the superior laryngeal, and below the vocal cords by the recurrent laryngeal nerve</p>\n<p><strong>Highyeild:</strong></p><p>Motor supply of larynx All intrinsic muscles of the larynx are supplied by the recurrent laryngeal nerve except for the cricothyroid Which is supplied by the external laryngeal nerve.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The inlet of the larynx is formed by:", "options": [{"label": "A", "text": "Ventricular Fold", "correct": false}, {"label": "B", "text": "Aryepiglottic Fold", "correct": true}, {"label": "C", "text": "Glossoepiglottic Fold", "correct": false}, {"label": "D", "text": "Vocal Cord", "correct": false}], "correct_answer": "B. Aryepiglottic Fold", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Aryepiglottic Fold Inlet of the larynx is bounded by: Anteriorly – Epiglottis Posteriorly – Interarytenoid fold of mucous membrane On each side by – Aryepiglotic fold</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 17 year old boy presented to the ENT department with a complaint of high pitched childhood voice which was psychologically affecting his personality. His mother was tensed and wanted the doctor to tell him why he did not have a voice like other boys of his age group. what is the most probable diagnosis?", "options": [{"label": "A", "text": "Hyponasality", "correct": false}, {"label": "B", "text": "Spasmodic Dysphonia", "correct": false}, {"label": "C", "text": "Puberphonia", "correct": true}, {"label": "D", "text": "Ventricular Dysphonia", "correct": false}], "correct_answer": "C. Puberphonia", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Puberphonia Normally, childhood voice has a higher pitch. When the larynx matures at puberty , vocal cords lengthen and the voice changes to one of lower pitch. This is a feature exclusive to males . Failure of this change leads to the persistence of childhood high-pitched voices and is called puberphonia.</p>\n<p><strong>Highyeild:</strong></p><p>PUBERPHONIA Adult with child-like voice/high-pitched voice. It is seen in boys who are emotionally immature, feel insecure, and show excessive fixation on their mothers. TREATMENT Speech therapy — if fails — guttzman’s technique ( Pressing the thyroid prominence backward in a downward direction causes the production of low tone voice) — if fails — thyroplasty.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A , Hyponasality is lack of resonance for words which resonate in the nasal cavity like m,n, ng Option: B . Spasmodic dysphonia is a condition associated with many other spasms in the body. Option: C . In Dysphonia plica ventricularis voice is produced by ventricular folds (false cords) which have taken over the function of true cords. The voice is rough, low-pitched, and unpleasant.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment is training the body to produce low- a pitched voice. Pressing the thyroid prominence in a backward and downward direction relaxes the overstretched cords and low tone voice can be produced (Gutzmann’s pressure test). The patient pressing on his larynx learns to produce a low-tone voice and then trains himself to produce syllables, words, and numbers. Prognosis is good.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 18 year old boy presented to the ENT department with a complaint of high pitched childhood voice which was psychologically affecting his personality. His mother was tensed and wanted the doctor to help why did he not have a voice like other boys of his age group? The diagnosis of mutational falsetto voice was made. All of the following can be used for management except:", "options": [{"label": "A", "text": "Training of the body to produce low pitched voice", "correct": false}, {"label": "B", "text": "Pressing the thyroid prominence in backward in downward direction causes production of low tone voice", "correct": false}, {"label": "C", "text": "Speech Therapy", "correct": false}, {"label": "D", "text": "Vocal Cord Excision", "correct": true}], "correct_answer": "D. Vocal Cord Excision", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Vocal Cord Excision Above case scenario describes puberphonia/mutational falsetto voice. TREATMENT Speech therapy — if fails — guttzman’s technique ( Pressing the thyroid prominence backward in a downward direction causes the production of low tone voice) — if fails — thyroplasty Vocal cord excision is not done.</p>\n<p><strong>Highyeild:</strong></p><p>PUBERPHONIA Adult with child-like voice/high-pitched voice. It is seen in boys who are emotionally immature, feel insecure, and show excessive fixation on their mothers.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A . Training of the body to produce a low-pitched voice is used for the treatment of puberphonia. Option: B .Pressing the thyroid prominence backward in a downward direction causes the production of a low-tone voice Guttzman’s technique is used if speech therapy fails. Option: C .Speech Therapy is used as the first-line treatment of puberphonia.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment is training the body to produce low- a pitched voice. Pressing the thyroid prominence in a backward and downward direction relaxes the overstretched cords and low tone voice can be produced (Gutzmann’s pressure test). The patient pressing on his larynx learns to produce a low-tone voice and then trains himself to produce syllables, words, and numbers. Prognosis is good.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient presented with stridor and dyspnea which he developed after an attack of upper respiratory tract infection. On examination, he was found to have a 3 mm glottic opening. All of the following are used in the management except", "options": [{"label": "A", "text": "Tracheostomy", "correct": false}, {"label": "B", "text": "Arytenoidectomy", "correct": false}, {"label": "C", "text": "Teflon Injection", "correct": true}, {"label": "D", "text": "Cordectomy", "correct": false}], "correct_answer": "C. Teflon Injection", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Teflon Injection Glottic diameter of 3 mm indicates that the patient is having laryngeal paralysis (due to URTI). Because of the narrowness of the opening, the patient is having stridor and dyspnea. For a quiet respiration, the glottic diameter should be 14 mm wide. Teflon injection is a method to medialize the cord and is therefore of no use in this patient. It would rather aggravate the condition .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Stridor and dyspnea can be managed by tracheostomy. Option: B. Stridor and dyspnea can also be managed by fixing the cord in a lateral position by adenoidectomy. Option: D. Stridor and dyspnea can also be managed by laser cordectomy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Laryngeal cartilage forming complete circle:", "options": [{"label": "A", "text": "Arytenoid", "correct": false}, {"label": "B", "text": "Cricoid", "correct": true}, {"label": "C", "text": "Thyroid", "correct": false}, {"label": "D", "text": "Hyoid", "correct": false}], "correct_answer": "B. Cricoid", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cricoid Cricoid cartilage is the only cartilage forming a complete ring. Its posterior part is expanded to form a lamina while anteriorly it is narrow forming an arch.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. arytenoid cartilage is pyramidal in shape. Option: C. thyroid cartilage is the largest cartilage. Its two alae meet anteriorly forming an angle of 90° in males and 120° in females. Option: D. hyoid is not cartilage. It’s a bone.</p>\n<p><strong>Extraedge:</strong></p><p>Thyroid, cricoid, and most of the arytenoid cartilages are hyaline cartilages. whereas epiglottis, corniculate, cuneiform, and tip of arytenoid near the corniculate cartilage are elastic fibrocartilage. Hyaline cartilages can undergo ossification; it begins at the age of 25 years in the thyroid, a little later in cricoids and arytenoids, and is complete by 65 years of age. Calcification seen in these cartilages can be confused with foreign bodies of the esophagus or larynx on X-rays.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A construction worker met with an accident and presented to the trauma centre when a heavy concrete block fell over his face. He was found to have severe maxillofacial and laryngeal injuries. He was not able to open his mouth and, on examination, he is found to have multiple fractures and obstruction in the nasopharynx as well as the oropharynx. In order to maintain a patent airway, the following procedure was done for him. Which of the following options defines the procedure correctly?", "options": [{"label": "A", "text": "Submental Endotracheal Intubation", "correct": false}, {"label": "B", "text": "Emergency Tracheostomy", "correct": false}, {"label": "C", "text": "Cricothyroidotomy", "correct": true}, {"label": "D", "text": "Subcutaneous Tracheostomy", "correct": false}], "correct_answer": "C. Cricothyroidotomy", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684993895802-QTDE002013IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cricothyroidotomy The given Image shows the incision is put on the cricothyroid membrane. So the procedure is cricothyroidotomy.</p>\n<p><strong>Highyeild:</strong></p><p>USES OF CRICOTHYROIDOTOMY Extreme Emergency Can be done in 10 seconds, to save the life of the person 1-2 second procedure needle puncture on cricothyroid membrane</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All of the following are Extrinsic Laryngeal Membranes/Ligaments, except", "options": [{"label": "A", "text": "Hyoepiglottic membrane", "correct": false}, {"label": "B", "text": "Cricothyroid membrane", "correct": true}, {"label": "C", "text": "Cricotracheal membrane", "correct": false}, {"label": "D", "text": "Thyrohyoid membrane", "correct": false}], "correct_answer": "B. Cricothyroid membrane", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cricothyroid membrane Cricothyroid membrane is part of intrinsic laryngeal membranes/ ligaments.</p>\n<p><strong>Highyeild:</strong></p><p>INTRINSIC MEMBRANE/LIGAMENTS OF LARYNX CRICOVOCAL MEMBRANE QUADRANGULAR MEMBRANE CRICOTHYROID MEMBRANE THYROEPIGLOTTIC MEMBRANE All intrinsic muscles of the larynx are supplied by the recurrent laryngeal nerve except for the cricothyroid which is supplied by the external laryngeal nerve.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: C. Cricotracheal membrane connects cricoid cartilage to the first tracheal ring. It is part of the external membrane/ligament of the larynx. Option: D. Thyrohyoid membrane connects thyroid cartilage to the hyoid bone. It is also the part of external membrane/ligament of the larynx. Option: A . Hyoepiglottic ligament attaches epiglottis to the hyoid bone. It is also the part of external membrane/ligament of the larynx.</p>\n<p><strong>Extraedge:</strong></p><p>Extrinsic membranes and ligaments (a) Thyrohyoid membrane . It connects thyroid cartilage to the hyoid bone. It is pierced by superior laryngeal vessels and internal laryngeal nerves. (b) Cricotracheal membrane . It connects the cricoid cartilage to the first tracheal ring. (c) Hyoepiglottic ligament . It attaches the epiglottis to the hyoid bone.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 20 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 12</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Anatomy Of Larynx (Part 2) - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 12</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 12 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 45 year old man was sitting and eating in a restaurant with his friends. He seemed to be an alcoholic and suddenly he suffocated and collapsed on the floor. On examination, his pulse was strong and his face began to turn blue. He is suddenly rolled into a prone position and with his hands interlocked pressure is exerted against the epigastrium 2-3 times and the food is expelled. What is this manoeuvre called?", "options": [{"label": "A", "text": "Fowler's Manoeuvre", "correct": false}, {"label": "B", "text": "Jendrassik Manoeuvre", "correct": false}, {"label": "C", "text": "Phalen's Manoeuvre", "correct": false}, {"label": "D", "text": "Heimlich Manoeuvre", "correct": true}], "correct_answer": "D. Heimlich Manoeuvre", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Heimlich Manoeuvre Stand behind the victim, pass your arms under his arms, and place your hands in front of the victim's epigastrium with one hand formed into a fist and the other hand lying over it. Now give 3 or 4 abdominal thrusts directed upwards and backward. By doing this, the residual air in the lungs is squeezed up in the trachea and larynx with force, dislodging foreign bodies and thus relieving laryngeal obstruction (choking). The foreign body is either expelled or removed.</p>\n<p><strong>Highyeild:</strong></p><p>SYMPTOMS AND SIGNS OF FOREIGN BODIES AT DIFFERENT LEVELS Site of foreign bodies Symptoms and signs Larynx ● Complete obstruction leading to death ● Partial obstruction: stridor, hoarseness, cough, respiratory difficulty Trachea ● Choking, stridor, wheeze, cough, palpatory thud, audible slap Bronchi ● Cough, wheeze and diminished air entry to lung forms a \"triad\" ● Respiratory distress with swelling of foreign body ● Lung collapse, emphysema, pneumonitis, bronchiectasis or lung abscess are late features</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Fowler maneuver : A test for tight intrinsic muscles in ulnar deviation of the digits. Option: B . Jendrassik’s maneuver : A procedure for emphasizing the patellar reflex; by enhancing intrinsic sympathetic tone. Option: C. Phalen's maneuver : For detection of carpal tunnel syndrome.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 27 year old pop singer who always amused people with his energetic voice experienced trouble with the voice. On examination, there were bilateral, symmetrical, soft, reddish and edematous nodules on his vocal cords. Which is the area of maximum contact between the vocal cords?", "options": [{"label": "A", "text": "Anterior One-Third", "correct": false}, {"label": "B", "text": "Posterior One-Third", "correct": false}, {"label": "C", "text": "The Junction between anterior two-thirds and posterior one-third", "correct": false}, {"label": "D", "text": "The junction between the anterior one-third and posterior two thirds", "correct": true}], "correct_answer": "D. The junction between the anterior one-third and posterior two thirds", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>The junction between the anterior one-third and posterior two thirds During vibration, the area of maximum contact between the vocal cords is at the junction of their anterior one-third and posterior two-third and thus subject to maximum friction. Hence in individuals, who overuse their voice, such as teachers, or pop singers, the inflammatory nodules develop at these sites called vocal/ singer’s/screamer’s nodules.</p>\n<p><strong>Highyeild:</strong></p><p>Vocal nodules. Typically, they form at the junction of anterior one-third with posterior two-thirds of vocal cord. Vocal nodules are bilateral and symmetrical and vary in size from that of pinhead to a split pea. In the early stages, they are soft, reddish, and oedematous but later become grayish or whitish in color.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 41 year old man had a 10-year history of relapsing polychondritis. He initially presented with hoarseness, dysphagia, fits of coughing, hearing loss, and tenderness over the thyroid cartilage and anterior trachea.CT showed irregular enlargement of both arytenoid cartilages to a lesser degree than enlargement of the thyroid and cricoid cartilages. Which structure of arytenoid cartilage articulates with cricoid cartilage?", "options": [{"label": "A", "text": "Base", "correct": true}, {"label": "B", "text": "Muscular Process", "correct": false}, {"label": "C", "text": "Vocal Process", "correct": false}, {"label": "D", "text": "Apex", "correct": false}], "correct_answer": "A. Base", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Base Arytenoid cartilages are Each arytenoid cartilage is pyramidal in shape. The base of arytenoid cartilage articulates with cricoid cartilage .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B . Muscular process, directed laterally to give attachment to intrinsic laryngeal muscles Option: C . Vocal process directed anteriorly, giving attachment to vocal cord Option: D. Apex which supports the corniculate cartilage, which further supports cuneiform.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 32 year old male was presented to the Emergency department with features of laryngospasm. He described the sensation of choking and being unable to speak. These episodes were usually after doing exercise and lasted for only a few minutes. The laryngeal muscle has undergone spasm. Which one among the following muscles is not paired?", "options": [{"label": "A", "text": "Transverse Arytenoid", "correct": true}, {"label": "B", "text": "Oblique Arytenoid", "correct": false}, {"label": "C", "text": "Aryepiglotticus", "correct": false}, {"label": "D", "text": "Thyroarytenoid", "correct": false}], "correct_answer": "A. Transverse Arytenoid", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Transverse Arytenoid All other options are paired intrinsic muscles of the larynx except the Transverse arytenoid being unpaired which adducts the vocal cords .</p>\n<p><strong>Highyeild:</strong></p><p>Laryngeal inlet and intrinsic muscles of larynx as seen from behind. Intrinsic muscles of larynx as seen on lateral view.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B & C .Oblique arytenoid and Aryepiglotticus are paired muscles that close the inlet of the larynx. Option: D. Thyroarytenoid is a paired muscle that relaxes the vocal cords.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Vestibular-evoked myogenic potential detects lesions of:", "options": [{"label": "A", "text": "Cochlear Nerve", "correct": false}, {"label": "B", "text": "Optic nerve", "correct": false}, {"label": "C", "text": "Inferior Vestibular Nerve", "correct": true}, {"label": "D", "text": "Inflammatory Myopathy", "correct": false}], "correct_answer": "C. Inferior Vestibular Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Inferior Vestibular Nerve Vestibular Evoked Myogenic Potentials This is a test to study the function of otolith organs Potential is generated in Sternocleidomastoid muscle . The vestibule is stimulated with sound (Saccule is stimulated). Saccule is supplied by Inferior Vestibular Nerve</p>\n<p><strong>Highyeild:</strong></p><p>Reflex arc is: From saccule—inferior vestibular—vestibular nuclei— ipsilateral vestibular spinal tract—spinal accessory nerve (CN XI)—sternocleidomastoid.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Ohngren’s line is described as an imaginary plane perpendicular to the intersection between", "options": [{"label": "A", "text": "The lateral canthus and menti", "correct": false}, {"label": "B", "text": "The medial canthus and angle of jaw", "correct": true}, {"label": "C", "text": "The lateral canthus and angle of jaw", "correct": false}, {"label": "D", "text": "The medial canthus and menti", "correct": false}], "correct_answer": "B. The medial canthus and angle of jaw", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>The medial canthus and angle of jaw Ohngren's line is between the medial canthus and the angle of the jaw. Tumours above this line are associated with a worse prognosis than those below the line.</p>\n<p><strong>Highyeild:</strong></p><p>Ohngren's line extends from medial canthus of eye to the angle of mandible. Growths anteroinferior to this plane (infrastruc- tural) have a better prognosis than those posterosuperior to it (supra- structural).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 35 year old male trumpet player presented to the emergency department with complaints of dysphagia, pain, hoarseness, a small mass on the neck, and difficulty in breathing. It was diagnosed as an external laryngocele. Through which membrane the herniation of saccule occurs?", "options": [{"label": "A", "text": "Thyrohyoid Membrane", "correct": true}, {"label": "B", "text": "Cricothyroid Membrane", "correct": false}, {"label": "C", "text": "Cricovocal Membrane", "correct": false}, {"label": "D", "text": "Quadrangular Membrane", "correct": false}], "correct_answer": "A. Thyrohyoid Membrane", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Thyrohyoid Membrane The laryngocele may be internal when it is located within the larynx or external when a distended saccule herniates through the thyrohyoid membrane and comes outside the larynx.</p>\n<p><strong>Highyeild:</strong></p><p>Laryngocele left side as seen on Valsalva (arrow). Laryngocele mixed type with internal and external components.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B & C .Cricovocal and cricothyroid membrane are located below the thyroid cartilage. Option: D .Quadrangular membrane not herniated by laryngocele.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 48 year old female presented to OPD with complaints of palpitation, fatigue, weight loss, stare, pretibial myxoedema, and diffusely enlarged thyroid. She had a partial thyroidectomy and later she lost her voice and had difficulty in breathing. Which nerve might have probably been injured in this patient?", "options": [{"label": "A", "text": "External laryngeal nerve", "correct": false}, {"label": "B", "text": "Recurrent laryngeal nerve unilaterally", "correct": false}, {"label": "C", "text": "Recurrent laryngeal nerve bilaterally", "correct": true}, {"label": "D", "text": "Both recurrent and external laryngeal nerve", "correct": false}], "correct_answer": "C. Recurrent laryngeal nerve bilaterally", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Recurrent laryngeal nerve bilaterally If recurrent laryngeal nerves are damaged bilaterally , both the vocal cords lie in the paramedian position with consequent loss of phonation and difficulty in breathing. It is often damaged, accidentally during partial thyroidectomy .</p>\n<p><strong>Highyeild:</strong></p><p>POSITION OF THE VOCAL CORD IN HEALTH AND DISEASE Situation in Position of the cord Location of the cord from midline Health Disease Median Midline Phonation RLN paralysis Paramedian 1.5 mm Strong whisper RLN paralysis Intermediate (cadaveric) 3.5 mm. This is neutral position of cricoarytenoid joint. Abduction and adduction take place from this position _ Paralysis of both recurrent and superior laryngeal nerves Gentle abduction 7mm Quiet respiration Paralysis of adductors Full abduction 9.5 mm Deep inspiration _</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A . Damage of external laryngeal nerve: If it is damaged, there is some weakness of phonation due to loss of tightening effect of cricothyroid muscle on the vocal cords. Option: B . If damaged unilaterally, the vocal cord on the affected side lies in the paramedian position and does not vibrate. But, usually, the other cord is able to compensate and the phonation is not much affected. The normal vocal cord moves freely and even crosses the midline to meet the paralyzed vocal cord; Option: D . If the recurrent and external laryngeal nerves are involved on both sides, and the vocal cords are further abducted and fixed due to paralysis of all intrinsic muscles of the larynx. This is known as the cadaveric position of vocal cords.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 56 year old male who is a chronic smoker and alcoholic, presented to OPD with throat pain, dysphagia, pain in the ear, weight loss, and breathlessness. It was later diagnosed as supraglottic carcinoma of the larynx. The supraglottic part of larynx is drained by:", "options": [{"label": "A", "text": "Upper deep cervical nodes", "correct": true}, {"label": "B", "text": "Pretracheal Nodes", "correct": false}, {"label": "C", "text": "Prelaryngeal Nodes", "correct": false}, {"label": "D", "text": "Lower deep cervical nodes", "correct": false}], "correct_answer": "A. Upper deep cervical nodes", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Upper deep cervical nodes Supraglottic larynx above the vocal cords is drained by upper deep cervical lymph nodes.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B, C & D - Infraglottic larynx below the vocal cords is drained by lymphatics which pierce the cricothyroid membrane and to prelaryngeal and pretracheal nodes and then to lower deep cervical and mediastinal nodes. Some vessels pierce through the orotracheal membrane and drain directly into lower deep cervical nodes .</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 32 year old male teacher presented to OPD with complaints of change in voice, and difficulty swallowing for more than 2 weeks. Laryngoscopic examination showed unilateral paralysis of the adductors of vocal cords. Pick out an adductor of the vocal cord:", "options": [{"label": "A", "text": "Posterior Cricoarytenoid", "correct": false}, {"label": "B", "text": "Lateral Cricoarytenoid", "correct": true}, {"label": "C", "text": "Thyroarytenoid", "correct": false}, {"label": "D", "text": "Oblique Arytenoid", "correct": false}], "correct_answer": "B. Lateral Cricoarytenoid", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lateral Cricoarytenoid Lateral cricoarytenoid is the Adductor of vocal cords.</p>\n<p><strong>Highyeild:</strong></p><p>Laryngeal inlet and intrinsic muscles of larynx as seen from behind. Intrinsic muscles of larynx as seen on lateral view.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Posterior cricoarytenoid is the only Abductor of vocal cords. Option: C . Thyroarytenoid-relax the vocal cords Option: D . Oblique arytenoid-closes the inlet of the larynx.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 23 year old female singer experienced hoarseness, change in voice, and difficulty in speech for 3 days. It was caused by paralysis of a laryngeal muscle which lies on the external surface of the larynx and is also known as a tuning fork of the larynx. Which nerve supplies this muscle of the larynx?", "options": [{"label": "A", "text": "External Laryngeal Nerve", "correct": true}, {"label": "B", "text": "Internal Laryngeal Nerve", "correct": false}, {"label": "C", "text": "Recurrent Laryngeal Nerve", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "A. External Laryngeal Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>External Laryngeal Nerve The cricothyroid is an important muscle for tone and pitch . When sound is about to be produced, it tenses the vocal cord and makes it ready to vibrate like a tuning fork . Hence it is also known as the tuning fork of the larynx . Paralysis of this muscle following an external laryngeal nerve lesion alters the voice quite significantly and is especially noticeable in singers.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B . Internal laryngeal nerve pierces the thyrohyoid membrane and is sensory to the mucosa of the pharynx and larynx. From the level of epiglottis to vocal folds. Option: C. All the intrinsic muscles of the larynx are supplied by recurrent laryngeal nerves except cricothyroid, which is supplied by the external laryngeal nerve.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which among the below-given options is not an intrinsic muscle of the larynx?", "options": [{"label": "A", "text": "Interarytenoid", "correct": false}, {"label": "B", "text": "Lateral Cricoarytenoid", "correct": false}, {"label": "C", "text": "Thyrohyoid", "correct": true}, {"label": "D", "text": "Thyroarytenoid", "correct": false}], "correct_answer": "C. Thyrohyoid", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Thyrohyoid The thyrohyoid muscle is an extrinsic muscle of the It is one of the muscles of the primary elevator of the larynx.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Interarytenoid muscle also called transverse arytenoid is an intrinsic muscle of the larynx. Option: B . Lateral cricoarytenoid muscle is an intrinsic muscle of the larynx. Option: D. Thyroarytenoid is also an intrinsic muscle of the larynx.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 22 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 10</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Anatomy of Middle Ear (Anterior Wall) - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 10</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 10 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "The Eustachian tube opens in?", "options": [{"label": "A", "text": "InnerEar", "correct": false}, {"label": "B", "text": "Middle Ear", "correct": true}, {"label": "C", "text": "Outer Ear", "correct": false}, {"label": "D", "text": "Larynx", "correct": false}], "correct_answer": "B. Middle Ear", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Middle Ear The eustachian tube opens into the middle ear.</p>\n<p><strong>Highyeild:</strong></p><p>MIDDLE EAR WALLS AND RELATIONS Walls of the middle ear and the structures related to them. (1) Canal for tensor tympani, (2) Opening of eustachian tube, (3) Oval window, (4) Round window, (5) Processus cochleariformis, (6) Horizontal canal, (7) Facial nerve, (8) Pyramid, (9) Aditus, (10) Chorda tympani, (11) Carotid artery, (12) Jugular bulb.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are true for Gradenigo’s syndrome except:", "options": [{"label": "A", "text": "Facial nerve is involved", "correct": true}, {"label": "B", "text": "It is caused by an abscess in the petrous apex", "correct": false}, {"label": "C", "text": "It leads to the involvement of the cranial nerves V and VI", "correct": false}, {"label": "D", "text": "It is characterized by retro-orbital pain", "correct": false}], "correct_answer": "A. Facial nerve is involved", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Facial nerve is involved. The facial nerve is not involved in Gradenigo’s syndrome.</p>\n<p><strong>Highyeild:</strong></p><p>PETROSITIS The Spread of infection from the middle ear and mastoid to the petrous part of the temporal bone is called petrositis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Gradenigo syndrome may be caused by an abscess in the petrous apex. Option: C. Gradenigo syndrome leads to involvement of the cranial nerve V and VI. This statement is true. Option: D. Gradenigo syndrome is characterized by retro-orbital pain due to the involvement of the 5th cranial nerve.</p>\n<p><strong>Extraedge:</strong></p><p>Gradenigo syndrome is the classical presentation and consists of a triad of (i) External rectus palsy (VIth nerve palsy), (ii) Deep-seated ear or retro-orbital pain (Vth nerve involvement) and (iii) Persistent ear discharge.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The narrowest part of the middle ear is:", "options": [{"label": "A", "text": "Hypotympanum", "correct": false}, {"label": "B", "text": "Epitympanum", "correct": false}, {"label": "C", "text": "ATTIC", "correct": false}, {"label": "D", "text": "Mesotympanum", "correct": true}], "correct_answer": "D. Mesotympanum", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mesotympanum Mesotympanum is the narrowest part of the middle ear measuring about 2 mm .</p>\n<p><strong>Highyeild:</strong></p><p>When seen in the coronal section, the cavity of the middle ear is biconcave, as the medial and lateral walls are closest to each other in the center. The distances separating them are: Near the roof 6 mm → Epitympanum (Attic) In the center 2 mm → Mesotympanum (between promontory and umbo) Near the floor 4 mm → Hypotympanum</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Hypotympanum measures about 4 mm. Option: B. Epitympanum measures about 6 mm. Option: C. epitympanum is also called an attic which measures about 6 mm.</p>\n<p><strong>Extraedge:</strong></p><p>Mesotympanum communicates with the attic via the anterior and posterior isthmi, situated in the membranous diaphragm between the mesotympanum and the attic.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Tegmen separates the middle ear from the middle cranial fossa containing the temporal lobe of the brain by:", "options": [{"label": "A", "text": "Medial wall of the middle ear", "correct": false}, {"label": "B", "text": "Lateral wall of the middle ear", "correct": false}, {"label": "C", "text": "Roof of the middle ear", "correct": true}, {"label": "D", "text": "Anterior wall of the middle ear", "correct": false}], "correct_answer": "C. Roof of the middle ear", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Roof of the middle ear The roof of the middle ear is formed by a thin plate of bone called TEGMEN TYMPANI . It separates the tympanic cavity from the middle cranial fossa. Tegmen tympani is formed by the squamous and petrous part of the temporal bone.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Processus cochleariformis attaches to:", "options": [{"label": "A", "text": "Tendon of tensor tympani", "correct": true}, {"label": "B", "text": "Basal turns of the helix", "correct": false}, {"label": "C", "text": "Handle of Malleus", "correct": false}, {"label": "D", "text": "INCUS", "correct": false}], "correct_answer": "A. Tendon of tensor tympani", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tendon of tensor tympani Anterior to the oval window lies a hook-like projection called the PROCESSUS COCHLEARIFORMIS for the tendon of the tensor tympani . The cochleariform process marks the level of the Genu of the facial nerve which is an important landmark for surgery .</p>\n<p><strong>Highyeild:</strong></p><p>Walls of the middle ear and the structures related to them. (1) Canal for tensor tympani, (2) Opening of eustachian tube, (3) Oval window, (4) Round window, (5) Processus cochleariformis, (6) Horizontal canal, (7) Facial nerve, (8) Pyramid, (9) Aditus, (10) Chorda tympani, (11) Carotid artery, (12) Jugular bulb.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The length of the Eustachian tube is:", "options": [{"label": "A", "text": "16 mm", "correct": false}, {"label": "B", "text": "24 mm", "correct": false}, {"label": "C", "text": "36 mm", "correct": true}, {"label": "D", "text": "40 mm", "correct": false}], "correct_answer": "C. 36 mm", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>36 mm The length of the Eustachian tube is 36 mm . The lateral third (i.e. 12 mm) is bony . Medial 2/3 (i.e. 24 mm) is fibrocartilaginous.</p>\n<p><strong>Highyeild:</strong></p><p>EUSTACHIAN TUBE It is a channel connecting the tympanic cavity with the nasopharynx. It is also called a pharyngotympanic tube. It is lined by Ciliated columnar epithelium. The length of the Eustachian tube is 36 mm (reached by the age of 7 years). The lateral third (i.e. 12 mm) is bony. Medial 2/3 (i.e. 24 mm) is fibrocartilaginous.</p>\n<p><strong>Extraedge:</strong></p><p>Muscles of the Eustachian tube are tensor veli palatini (dilator tube is a part of it) supplied by the branch of the mandibular nerve and levator veli palatini supplied by the pharyngeal plexus through the XIth cranial nerve.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Study the HRCT, what is the pointer depicting:", "options": [{"label": "A", "text": "Prussak Space", "correct": true}, {"label": "B", "text": "Mesotympanum", "correct": false}, {"label": "C", "text": "Vestibule", "correct": false}, {"label": "D", "text": "Internal Auditory Meatus", "correct": false}], "correct_answer": "A. Prussak Space", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996310146-QTDE014007IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Prussak Space Prussak's space lies medial to pars flaccida , lateral to the neck of the malleus, and above the lateral process of the malleus. Anteriorly, posteriorly and superiorly, it is bounded by a lateral malleal ligament . Posteriorly, it also has a gap through which the space communicates with epitympanum</p>\n<p><strong>Highyeild:</strong></p><p>PRUSSACK SPACE Boundaries : Superior: Lateral Malleolar fold Lateral : Pars flaccid of TM/ Scutum Medial: Neck of Malleus Inferior: Lateral process of Malleus</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Promontory seen in the middle ear is:", "options": [{"label": "A", "text": "Jugular Bulge", "correct": false}, {"label": "B", "text": "Basal turn of cochlea", "correct": true}, {"label": "C", "text": "Semicircular Canal", "correct": false}, {"label": "D", "text": "Head of Incus", "correct": false}], "correct_answer": "B. Basal turn of cochlea", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Basal turn of cochlea Promontory is seen in the medial wall of the middle ear and is due to the basal coil of the cochlea.</p>\n<p><strong>Extraedge:</strong></p><p>Walls of the middle ear and the structures related to them. (1) Canal for tensor tympani, (2) Opening of eustachian tube, (3) Oval window, (4) Round window, (5) Processus cochleariformis, (6) Horizontal canal, (7) Facial nerve, (8) Pyramid, (9) Aditus, (10) Chorda tympani, (11) Carotid artery, (12) Jugular bulb.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The effective area of the tympanic membrane:", "options": [{"label": "A", "text": "25 mm2", "correct": false}, {"label": "B", "text": "30 mm2", "correct": false}, {"label": "C", "text": "40 mm2", "correct": false}, {"label": "D", "text": "45 mm2", "correct": true}], "correct_answer": "D. 45 mm2", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>45 mm2 Area of tympanic membrane is 90 mm2. The effective area is 55 mm2 - (approximately 2/3rd of the total area) .</p>\n<p><strong>Highyeild:</strong></p><p>Significance of large area of tympanic membrane - The area of the tympanic is much larger than the area of the stapes footplate, which helps in converting sound of greater amplitude but a lesser force to that of lesser amplitude and great force.</p>\n<p><strong>Extraedge:</strong></p><p>The tympanic membrane consists of three layers: Outer epithelial layer, which is continuous with the skin lining the meatus. Inner mucosal layer, which is continuous with the mucosa of the middle ear. The middle fibrous layer encloses the handle of the malleus and has three types of fibers— radial, circular, and parabolic.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The cone of light is due to:", "options": [{"label": "A", "text": "Malleolar Fold", "correct": false}, {"label": "B", "text": "Handle of Malleus", "correct": true}, {"label": "C", "text": "Anterior Inferior Quadrant", "correct": false}, {"label": "D", "text": "Stapes", "correct": false}], "correct_answer": "B. Handle of Malleus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Handle of Malleus The cone of Light is seen in the anteroinferior quadrant of the tympanic membrane.</p>\n<p><strong>Highyeild:</strong></p><p>CONE OF LIGHT The cone of Light is seen in the anteroinferior quadrant of the tympanic membrane. It is the reflection of the light projected into the ear canal to examine it. This part reflects it because it is the only part of the tympanic membrane that is approximately at right angles to the meatus. This difference in different parts of the tympanic membrane is due to the handle of the malleus which pulls the tympanic membrane and causes it to tent inside.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 20 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 13</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Anatomy of Middle Ear - (Medial & Posterior Walls) - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 13</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 13 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A patient with cholesteatoma also complained of tinnitus in the same ear. While performing surgery to remove cholesteatoma from the middle ear you decided to damage the tympanic plexus to relieve tinnitus. All of the following nerves contribute to the Tympanic Plexus except:", "options": [{"label": "A", "text": "Tympanic branch of Glossopharyngeal nerve", "correct": false}, {"label": "B", "text": "Superior Caroticotympanic nerve", "correct": false}, {"label": "C", "text": "Inferior Caroticotympanic nerve", "correct": false}, {"label": "D", "text": "Tympanic branch of Facial nerve", "correct": true}], "correct_answer": "D. Tympanic branch of Facial nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tympanic branch of Facial nerve The tympanic plexus is not formed by the tympanic branch of facial nerve.</p>\n<p><strong>Highyeild:</strong></p><p>TYMPANIC PLEXUS The tympanic plexus is formed by 3 nerves Tympanic branch of Glossopharyngeal nerve Sympathetic from internal carotid plexus forms- Superior and Inferior caroticotympanic nerve.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Tympanic branch of the Glossopharyngeal nerve forms part of the tympanic plexus. Option: B. Superior Caroticotympanic nerve also forms part of the tympanic plexus. Option: C. Tympanic branch of the Facial nerve also forms part of the tympanic plexus.</p>\n<p><strong>Extraedge:</strong></p><p>Course of secretomotor fibres to the parotid Inferior salivary nucleus → CN IX → Tympanic branch → Tympanic plexus → Lesser petrosal nerve → Otic ganglion → Auriculotemporal nerve → Parotid gland.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An ENT surgeon notices that the floor of the middle ear is deficient when raising the inferior portion of a tympanometry flap. Which of the following structures is likely to be injured?", "options": [{"label": "A", "text": "Eustachian tube", "correct": false}, {"label": "B", "text": "Internal jugular vein", "correct": true}, {"label": "C", "text": "Cochlea", "correct": false}, {"label": "D", "text": "Tympanic membrane", "correct": false}], "correct_answer": "B. Internal jugular vein", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Internal jugular vein The internal jugular vein is likely to be injured in this given clinical scenario. The floor of the middle ear is related to the internal jugular vein . Occasionally, the floor is deficient and the jugular bulb is then covered only by fibrous tissue and a mucous membrane making it susceptible to injury during middle ear surgery.</p>\n<p><strong>Highyeild:</strong></p><p>MIDDLE EAR RELATIONS</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Eustachian tube is related to the anterior wall of the middle ear. Option: C. Cochlea is related to the medial wall of the middle ear. Option: D . Tympanic membrane is related to the lateral wall of the middle ear.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 60-year man comes to your clinic , while examination you suspect cholesteatoma. Which of the following is the most common site for residual cholesteatoma after surgery?", "options": [{"label": "A", "text": "Facial Recess", "correct": false}, {"label": "B", "text": "Hypotympanum", "correct": false}, {"label": "C", "text": "Sinus Tympani", "correct": true}, {"label": "D", "text": "Prussack's Space", "correct": false}], "correct_answer": "C. Sinus Tympani", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Sinus Tympani Sinus tympani is the most common site for residual cholesteatoma after surgery.</p>\n<p><strong>Highyeild:</strong></p><p>SINUS TYMPANI It is bounded by: Pyramid and vertical segment of facial nerve laterally The stapes & oval window medially Ponticulus superiorly Subiculum inferiorly</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Facial recess or the posterior sinus is a depression in the posterior wall lateral to the pyramid. It is bounded medially by the vertical part of the VIIth nerve, laterally by the chorda tympani, and above, by the fossa includes. Option: B. hypotympanum lies below the level of pars tensa. Option: D. Prussak’s space lies medial to pars flaccida, lateral to the neck of the malleus, and above the lateral process of the malleus. Anteriorly, posteriorly and superiorly, it is bounded by a lateral malleal ligament.</p>\n<p><strong>Extraedge:</strong></p><p>The involvement of sinus tympani , along with posterior mesotympanum, is a recognized risk factor for residual cholesteatoma . This is because it is particularly difficult to remove the disease from these areas.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "While performing a stapedotomy on a patient with otosclerosis. The structure present between the Ponticulus and Subiculum is called promontory. Which of the following forms the promontory in the middle ear cavity?", "options": [{"label": "A", "text": "Basal turn of the cochlea", "correct": true}, {"label": "B", "text": "Semicircular Canal", "correct": false}, {"label": "C", "text": "Handle of the malleus", "correct": false}, {"label": "D", "text": "Short process of the incus", "correct": false}], "correct_answer": "A. Basal turn of the cochlea", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Basal turn of the cochlea Promontory is seen in the medial wall of the middle ea r and is due to the basal coil of the cochlea.</p>\n<p><strong>Highyeild:</strong></p><p>The medial wall of the middle ear is formed by the labyrinth. The main features on the medial wall are: A bulge called a promontory formed by the basal turn of the cochlea. Fenestra vestibuli (oval window) lies posterosuperior to the promontory and opens into scala vestibuli. It is occupied by the footplate of stapes which is fixed by the annular ligament. The oval window is kidney-shaped Fenestra cochleae (round window) lies posteroinferior, to the promontory and opens into the scala tympani of cochlea. It is closed by a secondary tympanic membrane. The round window is closest to the ampulla of the posterior semicircular canal. Prominence of the facial nerve canal lies above the fenestra vestibuli curving downward into the posterior wall of the middle ear. Anterior to oval window lies a hook-like projection called the processus cochleariformis for the tendon of tensor tympani\".</p>\n<p><strong>Extraedge:</strong></p><p>The medial wall of the middle ear. (1) Promontory, (2) Proces- sus cochleariformis, (3) CN VII, (4) Oval window, (5) Horizontal canal, (6) Pyramid, (7) Ponticulus, (8) Sinus tympani, (9) Subiculum, (10) Round window, (11) Tympanic plexus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient with nasopharyngeal carcinoma on a CT scan revealed that the tumor has undergone a local spread, destroying the anterior wall of the middle ear. Which of the following structures is least likely to be involved?", "options": [{"label": "A", "text": "Eustachian Tube", "correct": false}, {"label": "B", "text": "Canal for tensor tympani", "correct": false}, {"label": "C", "text": "Internal carotid artery", "correct": false}, {"label": "D", "text": "Prominence of the facial canal", "correct": true}], "correct_answer": "D. Prominence of the facial canal", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Prominence of the facial canal The prominence of the facial canal is least likely to be injured in this given clinical scenario because it is a part of the medial wall of the middle ear and not the anterior wall.</p>\n<p><strong>Highyeild:</strong></p><p>The anterior wall of the middle ear is related to the following structures: Eustachian tube Internal carotid artery canal for tensor tympani</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Eustachian tube is related to the anterior wall of the middle ear. Option: B. The canal for tensor tympani is also related to the anterior wall of the middle ear. Option: C. Internal carotid artery is also related to the anterior wall of the middle ear.</p>\n<p><strong>Extraedge:</strong></p><p>Walls of the middle ear and the structures related to them. (1) Canal for tensor tympani, (2) Opening of eustachian tube, (3) Oval window, (4) Round window, (5) Processus cochleariformis, (6) Horizontal canal, (7) Facial nerve, (8) Pyramid, (9) Aditus, (10) Chorda tympani, (11) Carotid artery, (12) Jugular bulb.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient with nasopharyngeal carcinoma on a CT scan reveals that the tumor has undergone a local spread, destroying the anterior wall of the middle ear. Which of the following structures is most likely to be involved?", "options": [{"label": "A", "text": "Eustachian Tube", "correct": true}, {"label": "B", "text": "Chorda Tympani", "correct": false}, {"label": "C", "text": "Pyramid", "correct": false}, {"label": "D", "text": "Processus cochleariformis", "correct": false}], "correct_answer": "A. Eustachian Tube", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Eustachian Tube The eustachian tube is most likely to be injured in this given clinical scenario because it is a part of the anterior wall of the middle ear .</p>\n<p><strong>Highyeild:</strong></p><p>The anterior wall of the middle ear is related to the following structures: Eustachian tube The canal for tensor tympani Internal carotid artery</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Chorda tympani is related to the lateral wall of the middle ear. Option: C. The pyramid is related to the posterior wall of the middle ear. Option: D. Processus cochleariformis is related to the medial wall of the middle ear.</p>\n<p><strong>Extraedge:</strong></p><p>Walls of the middle ear and the structures related to them. (1) Canal for tensor tympani, (2) Opening of eustachian tube, (3) Oval window, (4) Round window, (5) Processus cochleariformis, (6) Horizontal canal, (7) Facial nerve, (8) Pyramid, (9) Aditus, (10) Chorda tympani, (11) Carotid artery, (12) Jugular bulb.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An ENT surgeon while performing mastoidectomy on a patient with complicated chronic suppurative otitis media he founds a false septum known as Korner's septum. Korner’s septum is the persistence of :", "options": [{"label": "A", "text": "Petrosquamous Suture", "correct": true}, {"label": "B", "text": "Petrotympanic Suture", "correct": false}, {"label": "C", "text": "Tympanomastoid Suture", "correct": false}, {"label": "D", "text": "Petromastoid Suture", "correct": false}], "correct_answer": "A. Petrosquamous Suture", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Petrosquamous Suture Korner septum is the persistence of petrosquamous suture. It separates the superficial squamous from deep petrous air cells.</p>\n<p><strong>Highyeild:</strong></p><p>KORNER SEPTUM It is the persistent petrosquamous suture. It separates the superficial squamous from deep petrous air cells. It is surgically important as it may cause difficulty in locating the antrum and the deeper cells, and thus lead to incomplete removal of disease at mastoidectomy. Mastoid antrum cannot be reached unless the Korner's septum has been removed.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45-year-old patient undergoing mastoidectomy. All are the boundaries of the supramental triangle except", "options": [{"label": "A", "text": "Temporal Line", "correct": false}, {"label": "B", "text": "Posterosuperior segment of the bony external auditory canal", "correct": false}, {"label": "C", "text": "Promontory", "correct": true}, {"label": "D", "text": "Tangent is drawn to the external auditory canal", "correct": false}], "correct_answer": "C. Promontory", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Promontory Promontory does not form the boundary of the supramental triangle. Promontory is the bulge produced by the basal turn of the cochlea on the medial wall of the middle ear.</p>\n<p><strong>Highyeild:</strong></p><p>MACEWEN’S/ SUPRAMEATAL TRIANGLE MacEwen's (supramental) triangle is bounded by: Superior: Temporal line Anterior: Posterosuperior segment of the bony external auditory canal. Posterior: The line is drawn as a tangent to the external canal. It is an important landmark to locate the mastoid antrum in a mastoid surge</p>\n<p><strong>Extraedge:</strong></p><p>It is an important landmark to locate the mastoid antrum in mastoid surgery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Regarding the stapedial reflex, which of the following is true:", "options": [{"label": "A", "text": "It helps to enhance the sound conduction in the middle ear", "correct": false}, {"label": "B", "text": "It is a protective reflex against loud sound", "correct": true}, {"label": "C", "text": "It is a unilateral reflex", "correct": false}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "B. It is a protective reflex against loud sound", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>It is a protective reflex against loud sound The Stapedius muscle helps to dampen very loud sounds and thus prevents noise trauma to the inner ear.</p>\n<p><strong>Highyeild:</strong></p><p>STAPEDIAL REFLEX/ ACOUSTIC REFLEX It is a protective reflex against loud sound The Stapedius muscle helps to dampen very loud sounds and thus prevents noise trauma to the inner ear. Ipsilateral: CN VIII → ventral cochlear nucleus → CN VII nucleus ipsilateral stapedius muscle. Contralateral: CN VIII → ventral cochlear nucleus → contralateral medial superior olivary nucleus → contralateral CN VII nucleus → contralateral stapedius muscle.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. stapedial reflex doesn’t help to enhance the sound conduction in the middle ear. Rather it is a protective reflex against loud sound. Option: C. stapedial reflex is not unilateral; rather it is a bilateral reflex.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The eustachian tube opens into the middle ear cavity at:", "options": [{"label": "A", "text": "Anterior Walls", "correct": true}, {"label": "B", "text": "Hypotympanum", "correct": false}, {"label": "C", "text": "Superior Surface", "correct": false}, {"label": "D", "text": "Posterior Wall", "correct": false}], "correct_answer": "A. Anterior Walls", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Anterior Walls The eustachian tube enters the middle ear cavity through the anterior wall.</p>\n<p><strong>Highyeild:</strong></p><p>The anterior wall of the middle ear is related to the following structures: Eustachian tube The canal for tensor tympani Internal carotid artery</p>\n<p><strong>Extraedge:</strong></p><p>Walls of the middle ear and the structures related to them. Canal for tensor tympani, Opening of eustachian tube, Oval window, Round window, Processus cochleariformis, Horizontal canal, Facial nerve, Pyramid, Aditus, Chorda tympani, Carotid artery, Jugular bulb.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Organ of Corti is situated in:", "options": [{"label": "A", "text": "Scala Media", "correct": true}, {"label": "B", "text": "Sinus Tympani", "correct": false}, {"label": "C", "text": "Sinus Vestibuli", "correct": false}, {"label": "D", "text": "Saccule", "correct": false}], "correct_answer": "A. Scala Media", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Scala Media The organ of Corti is located in the scala media of the cochlea of the inner ear between the vestibular duct and the tympanic duct and is composed of mechanosensory cells, known as hair cells.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Stapes footplate covers:", "options": [{"label": "A", "text": "Round Window", "correct": false}, {"label": "B", "text": "Oval Window", "correct": true}, {"label": "C", "text": "Inferior Sinus Tympani", "correct": false}, {"label": "D", "text": "Pyramid", "correct": false}], "correct_answer": "B. Oval Window", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Oval Window The footplate of the stapes covers the oval window.</p>\n<p><strong>Highyeild:</strong></p><p>RELATIONS OF MIDDLE EAR Walls of the middle ear and the structures related to them. (1) Canal for tensor tympani, (2) Opening of eustachian tube, (3) Oval window, (4) Round window, (5) Processus cochleariformis, (6) Horizontal canal, (7) Facial nerve, (8) Pyramid, (9) Aditus, (10) Chorda tympani, (11) Carotid artery, (12) Jugular bulb.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following conditions causes maximum hearing loss?", "options": [{"label": "A", "text": "Ossicular disruption with intact tympanic membrane", "correct": true}, {"label": "B", "text": "Tympanic membrane perforation", "correct": false}, {"label": "C", "text": "Complete obstruction of the ear canal", "correct": false}, {"label": "D", "text": "Ossicular interruption with perforation", "correct": false}], "correct_answer": "A. Ossicular disruption with intact tympanic membrane", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ossicular disruption with intact tympanic membrane Ossicular disruption with an intact tympanic membrane causes maximum hearing loss (around 54 dB) out of all the given options.</p>\n<p><strong>Highyeild:</strong></p><p>Average hearing loss seen in different lesions of conductive apparatus: Condition Average/hearing loss Closure of oval window 60 dB Ossicular interruption with intact TM 54dB Ossicular interruption with perforation 38 dB Complete obstruction of ear canal 30 dB TM perforation 10-40 dB</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Tympanic membrane perforation cause average hearing loss of 10-40 dB Option: C. Complete obstruction of ear canal cause average hearing loss of 30 dB Option: D. Ossicular interruption with perforation cause average hearing loss of 38 dB</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 23 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 16</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Anatomy of Sinunes - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 16</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 16 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Facial recess or the posterior sinus is bounded by:", "options": [{"label": "A", "text": "Medially by the vertical part of the facial nerve", "correct": false}, {"label": "B", "text": "Laterally by the chorda tympani", "correct": false}, {"label": "C", "text": "Above by the fossa incudis", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above Facial recess or Posterior sinus - It is a depression in the posterior wall of the middle ear. It is bounded by: Medially - Vertical part of facial nerve Laterally - Chorda tympani Above - Fossa incudis</p>\n<p><strong>Highyeild:</strong></p><p>FACIAL RECESS (A) Facial recess lies lateral and sinus tympani medial to the pyramidal eminence and vertical part of the facial nerve. (B) Exposure of facial recess through posterior tympanotomy as seen at mastoid surgery. SCC, semicircular canal.</p>\n<p><strong>Extraedge:</strong></p><p>Importance - This recess is important surgically, as direct access can be made through this into the middle ear without disturbing the posterior canal wall.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The direction of the nasolacrimal duct is:", "options": [{"label": "A", "text": "Downward, backward and medially", "correct": false}, {"label": "B", "text": "Downward, backward and laterally", "correct": true}, {"label": "C", "text": "Downward, forward and medially", "correct": false}, {"label": "D", "text": "Downward, forward and laterally", "correct": false}], "correct_answer": "B. Downward, backward and laterally", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Downward, backward and laterally Nasolacrimal duct runs downward, backward and laterally and opens in the inferior meatus of the nose</p>\n<p><strong>Highyeild:</strong></p><p>NASOLACRIMAL DUCT It is a membranous passage that begins at the lower end of the lacrimal sac. It runs downward, backward and laterally and opens in the inferior meatus of the nose.</p>\n<p><strong>Extraedge:</strong></p><p>A fold of mucous membrane called the valve of Hasner forms an imperfect value at the lower end of the duct.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Antrum of Highmore is another name for:", "options": [{"label": "A", "text": "Maxillary Sinus", "correct": true}, {"label": "B", "text": "Ethmoid Sinus", "correct": false}, {"label": "C", "text": "Sphenoid Sinus", "correct": false}, {"label": "D", "text": "Frontal Sinus", "correct": false}], "correct_answer": "A. Maxillary Sinus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Maxillary Sinus Maxillary sinus is also called the antrum of Highmore and is the first to develop in the human fetus. It is the largest paranasal sinus (15 ml capacity in adults).</p>\n<p><strong>Highyeild:</strong></p><p>MAXILLARY SINUS It is the largest of the paranasal sinuses and occupies the body of the maxilla. It is pyramidal in shape with a base towards the lateral wall of the nose and an apex directed laterally into the zygomatic process of the maxilla. On average, the maxillary sinus has a capacity of 15 mL in an adult. It is 33 mm high, 35 mm deep, and 25 mm wide.</p>\n<p><strong>Extraedge:</strong></p><p>R ELATIONS Anterior wall - the facial surface of the maxilla and is related to the soft tissues of the cheek. Posterior wall - infratemporal and pterygopalatine fossa. Medial wall - middle and inferior meatuses. Floor - alveolar and palatine processes of the maxilla and is situated about 1 cm below the level of the floor of the nose Roof - floor of the orbit.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In acute sinusitis, the sinus most often involved in children is", "options": [{"label": "A", "text": "Maxillary", "correct": false}, {"label": "B", "text": "Sphenoid", "correct": false}, {"label": "C", "text": "Ethmoid", "correct": true}, {"label": "D", "text": "Frontal", "correct": false}], "correct_answer": "C. Ethmoid", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ethmoid Most common sinusitis in children is Ethmoid Ethmoidal sinuses are well developed at birth , hence infants and children below 3 years of age are more likely to have acute ethmoiditis ; but after this age, maxillary antral infections are more commonly seen. Ethmoid sinuses are more often involved in infants and young children.</p>\n<p><strong>Highyeild:</strong></p><p>ETHMOID SINUS Ethmoidal sinuses are thin-walled air cavities in the lateral masses of the ethmoid bone. number varies from 3 to 18. They occupy the space between the upper third of the lateral nasal wall and the medial wall of the orbit. Clinically, ethmoidal cells are divided into the anterior ethmoid group which opens into the middle meatus, and the posterior ethmoid group which opens into the superior meatus and sphenoethmoidal recess.</p>\n<p><strong>Extraedge:</strong></p><p>Relations of Ethmoidal Sinuses Roof - anterior cranial fossa, lateral to the cribriform plate. Meninges of the brain form important relations here. Lateral wall - related to the orbit. The thin paper like lamina of bone (lamina papyracea) separating air cells from the orbit can be easily destroyed leading to the spread of ethmoidal infections into the orbit. Optic nerve forms a close relationship with the posterior ethmoidal cells and is at risk during ethmoid surgery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Periodicity is a characteristic feature of which sinus infection:", "options": [{"label": "A", "text": "Maxillary Sinus Infection", "correct": false}, {"label": "B", "text": "Frontal sinus infection", "correct": true}, {"label": "C", "text": "Sphenoid Sinus Infection", "correct": false}, {"label": "D", "text": "Ethmoid Sinus Infection", "correct": false}], "correct_answer": "B. Frontal sinus infection", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Frontal sinus infection Pain of frontal sinusitis shows characteristic periodicity , comes upon waking, gradually increases and reaches its peak by midday, and then starts subsiding. It is also called \" office headache \" as it is present only during office hours.</p>\n<p><strong>Highyeild:</strong></p><p>FRONTAL SINUS Each frontal sinus is situated between the inner and outer tables of the frontal bone, above and deep to the supraorbital margin. It varies in shape and size and is often loculated. The loculations are also called scallops. The two frontal sinuses are often asymmetric and the intervening bony septum is thin and often obliquely placed or may even be deficient. The frontal sinus may be absent on one or both sides or it may be very large extending into the orbital plate in the roof of the orbit.</p>\n<p><strong>Extraedge:</strong></p><p>Anterior wall of the sinus is related to the skin over the forehead; the inferior wall, to the orbit and its contents; and the posterior wall to the meninges and frontal lobe of the brain. Opening of the frontal sinus is situated in its floor and leads into the middle meatus directly or through a canal called the frontonasal duct.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Mucocele is commonly seen in which sinus:", "options": [{"label": "A", "text": "Frontal", "correct": true}, {"label": "B", "text": "Maxillary", "correct": false}, {"label": "C", "text": "Ethmoid", "correct": false}, {"label": "D", "text": "Sphenoid", "correct": false}], "correct_answer": "A. Frontal", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Frontal Mucoceles are most commonly formed in the Frontal sinus followed by ethmoid, sphenoid, and maxillary sinuses.</p>\n<p><strong>Highyeild:</strong></p><p>A mucocele is an epithelial lined, mucus-containing sac completely filling the sinus and capable of expansion: Mucoceles are most commonly formed in the Frontal sinus followed by ethmoid, sphenoid, and maxillary sinuses. Mucocele of the frontal sinus presents as a swelling in the floor of the frontal sinus above the inner (medial) canthus. It displaces the eyeball forward, downward, and laterally. Two important signs are: eggshell crackling sign loss of scallops IOC = CT scan TOC = Endoscopic sinus surgery Mucocele of frontal sinus. Note swelling above the medial canthus of left eye (arrow). CT scan of mucocele of the left frontoethmoid region. Note the left eyeball has been displaced downwards and laterally (arrows) (different patient).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Sinuses present birth is/are-", "options": [{"label": "A", "text": "Frontal sinus", "correct": false}, {"label": "B", "text": "Maxillary+Ethmoidal", "correct": true}, {"label": "C", "text": "Maxillary+Ethmoid Frontal", "correct": false}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "B. Maxillary+Ethmoidal", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Maxillary+Ethmoidal Maxillary and ethmoid sinuses are present at birth , while sphenoid sinus is rudimentary at birth and frontal sinus is recognizable at 6 years of age and is fully developed by puberty.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Frontal sinus is not present at birth. OPTION C- Maxillary and ethmoid sinus are present at birth but frontal sinus is not present.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are pneumatization patterns of sphenoid sinus except", "options": [{"label": "A", "text": "Conchal", "correct": false}, {"label": "B", "text": "Preconchal", "correct": true}, {"label": "C", "text": "Sellar", "correct": false}, {"label": "D", "text": "Presellar", "correct": false}], "correct_answer": "B. Preconchal", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Preconchal Preconchal is not the pneumatization pattern of the sphenoid sinus.</p>\n<p><strong>Highyeild:</strong></p><p>PNEUMATIZATION PATTERNS OF SPHENOID SINUS Sellar type (most common). Presellar type Conchal type</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "What is the name of a cell marked in a CT scan?", "options": [{"label": "A", "text": "Onodi Cell", "correct": false}, {"label": "B", "text": "Haller Cell", "correct": true}, {"label": "C", "text": "Aggarnasi", "correct": false}, {"label": "D", "text": "Bulla ethmoidalis", "correct": false}], "correct_answer": "B. Haller Cell", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685000240424-QTDE022009IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Haller Cell Haller cells , also known as infraorbital ethmoidal air cells , are ethmoid air cells located lateral to the maxillo-ethmoidal suture along the inferomedial orbital floor</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Onodi cell is a posterior ethmoidal cell that may grow posteriorly by the side of the sphenoid sinus or superior to it for as much distance as 1.5 cm from the anterior surface of the sphenoid. Onodi cell is surgically important as the optic nerve may be related to its lateral wall. OPTION C- Agger nasi air cells are the most anterior ethmoidal air cells lying anterolateral and inferior to the frontal recess and anterior and above the attachment of the middle turbinate. They are located within the lacrimal bone and therefore have as lateral relations the orbit, the lacrimal sac, and the nasolacrimal duct. OPTION D- It is an ethmoidal cell situated behind the uncinate process. The anterior surface of the bulla forms the posterior boundary of hiatus semilunaris. Depending on pneumatization, bulla may be a pneumatized cell or a solid bony prominence.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Pott's puffy tumour is a complication of", "options": [{"label": "A", "text": "Orbital Cellulitis", "correct": false}, {"label": "B", "text": "Ethmoid Sinusitis", "correct": false}, {"label": "C", "text": "Frontal sinusitis", "correct": true}, {"label": "D", "text": "Adenoid Cystic Carcinoma", "correct": false}], "correct_answer": "C. Frontal sinusitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Frontal sinusitis Osteomyelitis of the frontal bone results from an acute infection of the frontal sinus either directly or through the venous route. It may follow trauma or surgery during acute infection. Pus may form externally under the periosteum as soft doughy swelling or internally as an extradural abscess k/a Pott's puffy tumour.</p>\n<p><strong>Highyeild:</strong></p><p>FRONTAL SINUS Each frontal sinus is situated between the inner and outer tables of the frontal bone, above and deep to the supraorbital margin. It varies in shape and size and is often loculated. The loculations are also called scallops. The two frontal sinuses are often asymmetric and the intervening bony septum is thin and often obliquely placed or may even be deficient. The frontal sinus may be absent on one or both sides or it may be very large extending into the orbital plate in the roof of the orbit.</p>\n<p><strong>Extraedge:</strong></p><p>Anterior wall of the sinus is related to the skin over the forehead; the inferior wall, to the orbit and its contents; and the posterior wall to the meninges and frontal lobe of the brain. Opening of the frontal sinus is situated in its floor and leads into the middle meatus directly or through a canal called the frontonasal duct.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which sinus is NOT a part of the paranasal sinus?", "options": [{"label": "A", "text": "Frontal", "correct": false}, {"label": "B", "text": "Ethmoid", "correct": false}, {"label": "C", "text": "Sphenoid", "correct": false}, {"label": "D", "text": "Pyriform", "correct": true}], "correct_answer": "D. Pyriform", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Pyriform Pyriform sinus is not a part of paranasal sinus. The pyriform sinus lies on either side of the larynx and extends from the pharyngoepiglottic fold to the upper end of the oesophagus.</p>\n<p><strong>Highyeild:</strong></p><p>PARANASAL SINUS Paranasal sinuses are air-containing cavities in certain bones of the skull. There are four on each side. Clinically, paranasal sinuses have been divided into two groups. Anterior group Posterior group Maxillary Frontal Anterior ethmoidal sinus Posterior ethmoidal sinus (opens in superior meatus) Sphenoid sinus (opens in sphenoethmoidal recess)</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are true regarding mucocele of frontal sinus except:", "options": [{"label": "A", "text": "Cystic Tender Swelling", "correct": true}, {"label": "B", "text": "Egg shell crackling can be elicited", "correct": false}, {"label": "C", "text": "Displaces the eyeball downward and laterally", "correct": false}, {"label": "D", "text": "Treatment is front ethmoidectomy", "correct": false}], "correct_answer": "A. Cystic Tender Swelling", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685000240707-QTDE022013IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cystic Tender Swelling Frontal Sinus Mucocele Presents as a firm, non-tender swelling in the superomedial quadrant of the orbit. Displacement of the eyeball—Forward, downward, and lateral i.e., proptosis. Dull, mild headache in the frontal region.</p>\n<p><strong>Highyeild:</strong></p><p>A mucocele is an epithelial lined, mucus-containing sac completely filling the sinus and capable of expansion: Mucoceles are most commonly formed in the Frontal sinus followed by ethmoid, sphenoid, and maxillary sinuses. Mucocele of the frontal sinus presents as a swelling in the floor of the frontal sinus above the inner (medial) canthus. It displaces the eyeball forward, downward, and laterally. Two important signs are: eggshell crackling sign loss of scallops IOC = CT scan TOC = Endoscopic sinus surgery Mucocele of frontal sinus. Note swelling above the medial canthus of left eye (arrow). CT scan of mucocele of the left frontoethmoid region. Note the left eyeball has been displaced downwards and laterally (arrows) (different patient).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION B- Eggshell crackling can be elicited in frontal sinus mucocele is a true statement. OPTION C- Frontal sinus mucocele displaces the eyeball Forward, downward, and lateral i.e., proptosis. OPTION D- Treatment is fronto ethmoidectomy is a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "What is the name of the surgery being done?", "options": [{"label": "A", "text": "Antral Puncture", "correct": false}, {"label": "B", "text": "Caldwell LUC", "correct": true}, {"label": "C", "text": "FESS", "correct": false}, {"label": "D", "text": "DCR", "correct": false}], "correct_answer": "B. Caldwell LUC", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685000240753-QTDE022014IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Caldwell LUC Caldwell–Luc operation is a process of opening the maxillary antrum through the canine fossa by sublabial approach and dealing with the pathology inside the antrum. Operation is also called anterior antrostomy as access to the maxillary sinus is made through the anterior wall of the sinus.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Antral puncture procedure involves puncturing the medial wall of the maxillary sinus in the region of the inferior meatus and irrigating the sinus. OPTION C- ethmoidal polyps are removed by endoscopic sinus surgery more popularly called functional endoscopic sinus surgery (FESS). OPTION D- Dacryocystorhinostomy (DCR) surgery is a procedure that aims to eliminate fluid and mucus retention within the lacrimal sac, and to increase tear drainage for relief of epiphora (water running down the face).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 23 year old patient with deviated nasal septum presents with a headache that starts upon waking and reaches its peak by about midday. On examination, she has upper eyelid oedema and tenderness on pressing just above the medial canthus. Which of the following X-ray views is preferred to best evaluate the sinus involved?", "options": [{"label": "A", "text": "Water's View", "correct": false}, {"label": "B", "text": "Caldwell View", "correct": true}, {"label": "C", "text": "Schuller's View", "correct": false}, {"label": "D", "text": "Water's view with mouth open", "correct": false}], "correct_answer": "B. Caldwell View", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685000241214-QTDE022015IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Caldwell View The given clinical scenario points to a diagnosis of frontal sinusitis . The best view for evaluating the frontal sinus is Caldwell's view. It is also known as an occipitofrontal view or nose-forehead position.</p>\n<p><strong>Highyeild:</strong></p><p>CALDWELL VIEW Caldwell view (Occipitofrontal view or nose-forehead position). The view is taken with the nose and forehead touching the film and the X-ray beam is projected 15–20° caudally.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Water’s view / occipitomental view or nose-chin position. It is taken in such a way that the nose and chin of the patient touch the film while the X-ray beam is projected from behind . OPTION C- Schuller’s view is similar to Law’s view but the cephalocaudal beam makes an angle of 30° to the sagittal. OPTION D- Waters’ view with an open mouth is preferred as it also shows sphenoid sinus. In this view, petrous bones are projected below the maxillary antrum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Identify X-ray view?", "options": [{"label": "A", "text": "Schuller View", "correct": false}, {"label": "B", "text": "Caldwell View", "correct": true}, {"label": "C", "text": "Laws View", "correct": false}, {"label": "D", "text": "Town View", "correct": false}], "correct_answer": "B. Caldwell View", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685000241401-QTDE022016IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Caldwell View Caldwell view (Occipitofrontal view or nose-forehead position) . The view is taken with the nose and forehead touching the film and the X-ray beam is projected 15–20 degrees caudally.</p>\n<p><strong>Highyeild:</strong></p><p>CALDWELL VIEW Caldwell view (Occipitofrontal view or nose-forehead position). The view is taken with the nose and forehead touching the film and the X-ray beam is projected 15–20° caudally.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - OPTION A- Schuller’s view is similar to Law’s view but the cephalocaudal beam makes an angle of 30° to the sagittal. OPTION C- Law’s view is a lateral oblique view of the mastoid. The patient lies in such a way that the sagittal plane of the skull is parallel to the film and the X-ray beam is projected 15° cephalocaudal. OPTION D- Towne’s view is an anteroposterior view with a 30° tilt from above and in front. It shows both petrous pyramids which can be compared.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Sinus not present at birth is", "options": [{"label": "A", "text": "Ethmoid", "correct": false}, {"label": "B", "text": "Maxillary", "correct": false}, {"label": "C", "text": "Sphenoid", "correct": true}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "C. Sphenoid", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Sphenoid Maxillary and ethmoid sinuses are present at birth , while sphenoid sinus is not present at birth and frontal sinus is recognizable at 6 years of age and is fully developed by puberty.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Ethmoid sinus is present at birth. OPTION B- Maxillary sinus is present at birth.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 26 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 7</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Anatomy of Throat & Neck Spaces - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 7</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 7 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Killian's dehiscence is seen in:", "options": [{"label": "A", "text": "Oropharynx", "correct": false}, {"label": "B", "text": "Nasopharynx", "correct": false}, {"label": "C", "text": "Cricopharynx", "correct": true}, {"label": "D", "text": "Vocal Cords", "correct": false}], "correct_answer": "C. Cricopharynx", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cricopharynx Killian’s dehiscence is an area of weakness between the two parts of the inferior constrictor muscle – thyropharyngeus and cricopharyngeus.</p>\n<p><strong>Highyeild:</strong></p><p>Hypopharyngeal (Zenker's) diverticulum. Hypopharyn- geal mucosa herniates through the Killian's dehiscence—a weak area between two parts of inferior constrictor muscle. Potential sites for hypopharyngeal diverticulum. A pulsion diverticulum of pharyngeal mucosa can emerge posteriorly through the Killian's dehiscence called Zenker's diverticulum or pharyngeal pouch . It is an area of weaknes s so it is one of the sites of esophageal perforation during instrumentation and Endoscopy hence also called the Gateway of Tears.</p>\n<p><strong>Extraedge:</strong></p><p>Killian-Janieson's space - It lies between the cricopharyngeus and circular fibers of the esophagus. Lamier Heckerman’s space - It lies between circular and longitudinal fibers of the esophagus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Identify the position of the patient during surgery and select the surgeries from the following list where it is used.", "options": [{"label": "A", "text": "Submucous Resection of the nasal septum", "correct": false}, {"label": "B", "text": "Neurosurgery", "correct": false}, {"label": "C", "text": "Tonsillectomy", "correct": true}, {"label": "D", "text": "Myringoplasty", "correct": false}], "correct_answer": "C. Tonsillectomy", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994030447-QTDE005002IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tonsillectomy In the figure is the ' Rose position ' where the patient lies supine with the head extended by placing a pillow under the shoulder.</p>\n<p><strong>Highyeild:</strong></p><p>Rose position is used during. I. Tonsillectomy II. Adenoidectomy III. Tracheostomy Rose's position for tonsillectomy. Neck is extended by a sand bag under the shoulders and the head is supported on a ring.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Rose position is not used in submucous resection of the nasal septum. Option: B . Rose position is not used; instead fowler’s position is used in neurosurgery. Option: D . Rose position is not used in myringoplasty.</p>\n<p><strong>Extraedge:</strong></p><p>TECHNIQUES OF TONSILLECTOMY/ TONSILLOTOMY ● Cold methods ○ Dissection and snare (most common) ○ Guillotine method ○ Intracapsular (capsule preserving) tonsillectomy with debrider ○ Harmonic scalpel (ultrasound) ○ Plasma-mediated ablation or dissection technique (coblation) ○ Cryosurgical technique ● Hot methods ○ Electrocautery ○ Laser tonsillectomy or tonsillotomy (CO2 or KTP) ○ Radiofrequency</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Vocal cords are lined by:", "options": [{"label": "A", "text": "Stratified Columnar Epithelium", "correct": false}, {"label": "B", "text": "Pseudostratified ciliated columnar epithelium", "correct": false}, {"label": "C", "text": "Stratified squamous epithelium", "correct": true}, {"label": "D", "text": "Cuboidal Epithelium", "correct": false}], "correct_answer": "C. Stratified squamous epithelium", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Stratified squamous epithelium Vocal cords and the upper part of the vestibule are lined by stratified squamous epithelium.</p>\n<p><strong>Highyeild:</strong></p><p>The epithelium of the larynx is lined by ciliated columnar epithelium. Except vocal cords and the upper part of the vestibule which are lined by stratified squamous epithelium.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A .Stratified Columnar Epithelium lines conjunctiva, lobar ducts. Option: B. Pseudostratified ciliated columnar epithelium lines respiratory epithelium. Option: D .Cuboidal Epithelium Lines kidney tubules.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Reinke's space is seen in:", "options": [{"label": "A", "text": "Vocal Cord", "correct": true}, {"label": "B", "text": "Tympanic Membrane", "correct": false}, {"label": "C", "text": "Cochlea", "correct": false}, {"label": "D", "text": "Reissner's Membrane", "correct": false}], "correct_answer": "A. Vocal Cord", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Vocal Cord Under the epithelium of vocal cords is a potential space called a reink’s space with scanty subepithelial connective tissues. It is bounded above and below by the arcuate lines , in front by the anterior commissure , and behind by the vocal process of the arytenoid.</p>\n<p><strong>Highyeild:</strong></p><p>REINKE’S EDEMA is diffuse edema of the Reinke's space (of vocal cords) leading to irreversible fusiform swelling of the vocal cord- usually bilateral. The patient has a low-pitched hoarse voice The most common cause – is smoking , others – are extraesophageal reflux, vocal strain, and hypothyroidism.</p>\n<p><strong>Extraedge:</strong></p><p>TREATMENT OF REINKE EDEMA Decortication of the vocal cords, i.e. removal of a strip of epithelium, is done first on one side and 3–4 weeks later on the other. Voice rest. Speech therapy for proper voice production.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "According to the European Laryngeal Society, subligamentous cordectomy is classified as:", "options": [{"label": "A", "text": "Type I", "correct": false}, {"label": "B", "text": "Type II", "correct": true}, {"label": "C", "text": "Type III", "correct": false}, {"label": "D", "text": "Type IV", "correct": false}], "correct_answer": "B. Type II", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Type II Type II cordectomy is sub-ligamental cordectomy which is a resection of epithelium, reink’s space, and vocal ligament.</p>\n<p><strong>Highyeild:</strong></p><p>The European Laryngological Society is proposing a classification of different laryngeal endoscopic cordectomies in order to ensure better definitions of post-operative results. Type I : A subepithelial cordectomy, which is a resection of the epithelium Type II : A sub-ligamental cordectomy, which is a resection of the epithelium, Reinke's space, and vocal ligament. Type III : Trans muscular cordectomy, which proceeds through the vocalis muscle Type IV: Total cordectomy; Type Va : Extended cordectomy, which encompasses the contralateral vocal fold and the anterior commissure Type Vb : Extended cordectomy, which includes the arytenoid Type Vc: Extended cordectomy, which encompasses the subglottis Type Vd: Extended cordectomy, which includes the ventricle.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Type I is subepithelial cordectomy. Option: C. Type III is transmuscular cordectomy. Option: D. Type IV is total cordectomy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient presents to your OPD with a complaint of severe pain in the throat and a unilateral abscess as shown below. You diagnosed it as Quinsy. All statements given below are true with respect to the condition, except:", "options": [{"label": "A", "text": "It is a collection of pus in the peritonsillar space", "correct": false}, {"label": "B", "text": "Marked odynophagia", "correct": false}, {"label": "C", "text": "Needle aspiration of the abscess is contraindicated, to avoid severe bleeding", "correct": true}, {"label": "D", "text": "Hot potato voice", "correct": false}], "correct_answer": "C. Needle aspiration of the abscess is contraindicated, to avoid severe bleeding", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994030480-QTDE005006IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Needle aspiration of the abscess is contraindicated, to avoid severe bleeding. Needle aspiration of abscess is done as it provides material for culture and sensitivity of bacteria. Immediate incision and drainage are C/I if a small child presents with quinsy and trismus . It is done after giving antibiotics for 24-48 hours.</p>\n<p><strong>Highyeild:</strong></p><p>Peritonsillar abscess left side. Peritonsillar abscess. Site of drainage is just lateral to the junction of vertical line through anterior pillar and horizontal line through base of uvula. PERITONSILLAR ABSCESS/QUINSY - It is a collection of pus in the peritonsillar space which lies between the capsule of the tonsil and the superior constrictor muscle.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Peritonsillar abscess is a collection of pus in the peritonsillar space. So option A is correct. Option: B. Marked pain on swallowing that is odynophagia is seen in quinsy. It is so marked that the patient cannot even swallow his own saliva. So option B is also correct. Option: D. Speech of the patient becomes thick and muffled also called a hot potato voice. So option D is also correct.</p>\n<p><strong>Extraedge:</strong></p><p>TREATMENT Hospitalization. Intravenous fluids to combat dehydration. Suitable antibiotics in large i.v. doses to cover both aerobic and anaerobic organisms. Analgesics like paracetamol are given for the relief of pain and to lower the temperature. Sometimes, stronger analgesics like pethidine may be required. Aspirin is avoided because of the danger of bleeding. Oral hygiene should be maintained by hydrogen perox-ide or saline mouthwashes.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You see a lot of cases of neck space infection in the ENT ward. To understand the anatomy behind it, you ask the PG for an explanation. The spaces in the neck in relation to the pharynx where abscesses can form are marked in the image below. Label the spaces correctly:", "options": [{"label": "A", "text": "A: Prevertebral space B: Parapharyngeal space C: Retropharyngeal space", "correct": true}, {"label": "B", "text": "A: Prevertebral space B: Retropharyngeal space c: Parapharyngeal space", "correct": false}, {"label": "C", "text": "A: Retropharyngeal space B: Parapharyngeal space C: Prevertebral space", "correct": false}, {"label": "D", "text": "A: Parapharyngeal space B: Prevertebral space C: Retropharyngeal space", "correct": false}], "correct_answer": "A. A: Prevertebral space B: Parapharyngeal space C: Retropharyngeal space", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994030511-QTDE005007IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>A: Prevertebral space B: Parapharyngeal space C: Retropharyngeal space Mark A - prevertebral space Mark B - parapharyngeal space Mark C- retropharyngeal space</p>\n<p><strong>Highyeild:</strong></p><p>SPACES OF NECK</p>\n<p><strong>Extraedge:</strong></p><p>The parapharyngeal space communicates with the retropharyngeal space. Infection of the retropharyngeal space can pass down behind the esophagus into the mediastinum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 17 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 5</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Anatomy of Throat & Tonsils - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 5</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 5 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "As given below in the image, the arrow-marked structure is a dimple above the adenoids. This arrow-marked structure is known as:", "options": [{"label": "A", "text": "Sinus of Morgagni", "correct": false}, {"label": "B", "text": "Pas Savant's Ridge", "correct": false}, {"label": "C", "text": "Rathke’S Pouch", "correct": true}, {"label": "D", "text": "Tubal Tonsil", "correct": false}], "correct_answer": "C. Rathke’S Pouch", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994134462-QTDE006001IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rathke’S Pouch Rathke’s pouch is represented clinically by a dimple above the adenoid and is reminiscent of buccal mucosal invagination, to form the anterior lobe of the Craniopharyngioma may arise from it.</p>\n<p><strong>Highyeild:</strong></p><p>Rathke's pouch is represented by a dimple, high in nasopharynx. Inferior to this, within the adenoid mass, is the naso- pharyngeal bursa.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A . Sinus of Morgagni is a space between the base of the skull and the upper free border of the superior constrictor muscle. The eustachian tube enters through it. Option: B. Passavant’s ridge is a mucosal ridge raised by fibers of the It encircles the posterior and lateral walls of the nasopharyngeal isthmus. Option: D. Tubal tonsil is a collection of subepithelial lymphoid tissue situated at the tubal elevation. It is continuous with adenoid tissue and forms a part of waldeyer ring.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Mr. Subhash, 45 years old male, nonhypertensive, nondiabetic patient had episodes of recurrent sore throat and pain in his throat episodes. He was scheduled for tonsillectomy and on the second day of surgery he started having a hemorrhage from tonsillar fossa. What can be the most likely cause of this problem?", "options": [{"label": "A", "text": "Slippage of ligature", "correct": true}, {"label": "B", "text": "injury to teeth", "correct": false}, {"label": "C", "text": "Infection", "correct": false}, {"label": "D", "text": "Bleeding Disorder", "correct": false}], "correct_answer": "A. Slippage of ligature", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Slippage of ligature Bleeding within 24 hours after tonsillectomy surgery is called a reactionary hemorrhage. The most common cause of reactionary hemorrhage after tonsillectomy is slippage of ligature .</p>\n<p><strong>Highyeild:</strong></p><p>Primary hemorrhage - occurs at the time of operation . From peritonsillar plexus of veins. Reactionary hemorrhage - Occurs within a period of 24 h and can be controlled by simple measures such as removal of the clot, application of pressure, or vasoconstrictor. Secondary hemorrhage - seen between the fifth to tenth postoperative The most common cause is infection.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Injury to teeth is one of the complications of tonsillectomy. Option: C . Infection is the most common cause of secondary hemorrhage. Option: D. Bleeding disorder can be the cause of bleeding but not the most common cause.</p>\n<p><strong>Extraedge:</strong></p><p>TECHNIQUES OF TONSILLECTOMY/ TONSILLOTOMY ● Cold methods ○ Dissection and snare (most common) ○ Guillotine method ○ Intracapsular (capsule preserving) tonsillectomy with debrider ○ Harmonic scalpel (ultrasound) ○ Plasma-mediated ablation or dissection technique (coblation) ○ Cryosurgical technique ● Hot methods ○ Electrocautery ○ Laser tonsillectomy or tonsillotomy (CO2 or KTP) ○ Radiofrequency</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 7-year-old patient came with complaints of right otalgia. The ear examination of the patient was not significant, however on oral cavity examination the patient had grade III congested tonsil bilaterally. The patient’s mother gives a history of recurrent sore throat. Which nerve is responsible for referred pain in the ear?", "options": [{"label": "A", "text": "Glossopharyngeal Nerve", "correct": true}, {"label": "B", "text": "Facial Nerve", "correct": false}, {"label": "C", "text": "Vagus Nerve", "correct": false}, {"label": "D", "text": "Auriculotemporal Nerve", "correct": false}], "correct_answer": "A. Glossopharyngeal Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Glossopharyngeal Nerve The glossopharyngeal nerve provides sensory nerve supply to the tonsils. The glossopharyngeal nerve also gives sensory supply to the middle ear (through the tympanic plexus). Therefore, pain from the tonsils can be referred to the middle ear along the glossopharyngeal nerve.</p>\n<p><strong>Highyeild:</strong></p><p>Referred causes of otalgia. Pain is referred via CNV (teeth, oral cavity, TM joint, anterior two-thirds of tongue), C2,3 (cervical spine), CN IX (tonsil, base of tongue, elongated styloid process) and CN X (vallecula, pyriform fossa or larynx).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B . Facial nerve is not involved in referred pain to the ear from any site. Option: C . Vagus nerve causing referred pain to ear from lesions of the epiglottis, larynx, and esophagus. Option: D . Auriculotemporal nerve causing referred pain to ear from dental, temporomandibular lesions.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 5-year-old boy was brought to OPD with a complaint of difficulty in nasal breathing. The kid has a high arch palate; on further inquiry, it was known that the boy sleeps with an open mouth and snoresn. A diagnosis of adenoid hypertrophy was made. Which of the following is not true about adenoids?", "options": [{"label": "A", "text": "Adenoids start visualizing at 5 months of age", "correct": false}, {"label": "B", "text": "Early adenoidectomy helps in developing immunity to pneumococcus.", "correct": true}, {"label": "C", "text": "Topical nasal steroids have a role in the management of the adenoid hypertrophy", "correct": false}, {"label": "D", "text": "Adenoid hypertrophy is highly suspicious of Immunodeficiency in an adult patient", "correct": false}], "correct_answer": "B. Early adenoidectomy helps in developing immunity to pneumococcus.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Early adenoidectomy helps in developing immunity to pneumococcus. The function of the lymphoid tissue of Waldeyer’s ring is to produce antibodies. The adenoid produces B-cells , giving rise to IgG and IgA plasma cells. Evidence supports the concern that early adenoidectomy produces a detectable negative effect on the development of serum IgG antibodies, resulting in impaired immunity to pneumococcus.</p>\n<p><strong>Highyeild:</strong></p><p>Enlarged adenoids (arrows) in a 7-year-old girl. There is very little breathing space in the nasopharynx. Adenoid tissue is seen on MRI in all infants by age of 5 months, gradually it increases in size and is at its maximum on 6-7 years. Starts regressing at puberty and disappears by the age of 15 years. Persistence of tissue may be seen beyond 15 years in cases of allergy or infection. Symptoms of adenoid disease depend on the comparative size of nasopharynx.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. The adenoid is visible using magnetic resonance imaging (MRI) from the age of 4 months in 18% of children. At 5 months of age, the adenoid could be identified in all children. Option: C. Topical nasal steroid sprays can cause a reduction in adenoid size with improvements in the presence of nasal obstruction, rhinorrhoea, cough, snoring, and sleep apnoea. Option: D. Adenoid hypertrophy is highly suspicious of immunodeficiency in adult patients.</p>\n<p><strong>Extraedge:</strong></p><p>Adenoids cause tubal dysfunction by: (a) Mechanical obstruction of the tubal opening. (b) Acting as a reservoir for pathogenic organisms. (c) In cases of allergy, mast cells of the adenoid tissue release inflammatory mediators which cause tubal blockage. Thus, adenoids can cause otitis media with effusion or recurrent acute otitis media. Adenoidectomy can help both of these conditions.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Waldeyer’s ring consists of all of the following except?", "options": [{"label": "A", "text": "Palatine Tonsils", "correct": false}, {"label": "B", "text": "Pharyngeal Tonsils", "correct": false}, {"label": "C", "text": "Tubal Tonsils", "correct": false}, {"label": "D", "text": "Post. Auricular nodes", "correct": true}], "correct_answer": "D. Post. Auricular nodes", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Post. Auricular nodes Waldeyer ring doesn’t consist of posterior auricular nodes.</p>\n<p><strong>Highyeild:</strong></p><p>Waldeyer's ring is a ring of lymphoid tissue located in the nasopharynx and oropharynx at the entrance to the aerodigestive tract. It consists of Palatine tonsil Adenoids / nasopharyngeal tonsil Lingual tonsil Tubal tonsil Lateral pharyngeal bands.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. palatine tonsils form the waldeyer ring. Option: B. pharyngeal tonsils also form the waldeyer ring. Option: C. tubal tonsils also form the waldeyer ring.</p>\n<p><strong>Extraedge:</strong></p><p>It is a collection of subepithelial lymphoid tissue situated at the tubal elevation. It is continuous with adenoid tissue and forms a part of Waldeyer’s ring. When enlarged due to infection, it causes eustachian tube occlusion.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 15 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 8</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Assistive Devices For Hearing - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 8</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 8 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "All the following options given below are Indications For Brainstem Implant Except:", "options": [{"label": "A", "text": "B/L acoustic neuroma in Neurofibromatosis-2", "correct": false}, {"label": "B", "text": "Absent Auditory Nerve", "correct": false}, {"label": "C", "text": "Absent Cochlea", "correct": false}, {"label": "D", "text": "Ossicular Fixation", "correct": true}], "correct_answer": "D. Ossicular Fixation", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ossicular Fixation In ossicular fixation, the cochlea and auditory nerves are intact so a brain stem implant is not needed. Ossicular reconstruction may be done as needed.</p>\n<p><strong>Highyeild:</strong></p><p>Indications for Auditory Brainstem Implant : B/L acoustic neuroma as in Neurofibromatosis- 2 Absent cochlea Absent auditory nerve Implanted in the recess of the IVth ventricle.</p>\n<p><strong>Extraedge:</strong></p><p>Brainstem implant is similar to “Nucleus” multichannel cochlear implant except that the multielectrode array is attached to a Dacron mesh, which is placed on the brainstem. The receiver/stimulator has a removable magnet so that MRI can be safely performed in such cases if the need arises.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A child aged 3 years presented with severe sensorineural deafness. He has been prescribed hearing aids but showed no improvement. What is the next line of management?", "options": [{"label": "A", "text": "Fenestration", "correct": false}, {"label": "B", "text": "Stapes Mobilization", "correct": false}, {"label": "C", "text": "Cochlear Implant", "correct": true}, {"label": "D", "text": "Conservative", "correct": false}], "correct_answer": "C. Cochlear Implant", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cochlear Implant As the child is having sensorineural deafness and even providing a hearing aid child is not beneficial. So we have to give a cochlear implant to a child provided there is no retro cochlear disease in the child.</p>\n<p><strong>Highyeild:</strong></p><p>Electrodes are implanted into the scala tympani. Indications Bilateral severe to profound sensorineural hearing loss. Little or no benefit from hearing aids. No medical contraindication for surgery. No retro cochlear disease</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Fenestration operation is done when the stapes footplate is fixed and the round window is functioning. Option: B. stapes mobilization is also done when stapes are fixed Option: No role of conservative management in above mentioned condition.</p>\n<p><strong>Extraedge:</strong></p><p>O UTCOMES OF C OCHLEAR I MPLANTATION . Factors that predict a successful clinical outcome are: Previous auditory experience (postlingual patients or prior use of hearing aids). Younger age at implantation (especially for prelingual children). Shorter duration of deafness. Neural plasticity within the auditory system.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following part of a cochlear implant is implanted during surgery?", "options": [{"label": "A", "text": "Receiver Stimulator", "correct": true}, {"label": "B", "text": "Transmitting Coil", "correct": false}, {"label": "C", "text": "Microphone", "correct": false}, {"label": "D", "text": "Speech Processor", "correct": false}], "correct_answer": "A. Receiver Stimulator", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Receiver Stimulator Receiver/Stimulator ( Implanted under the skin) and Electrode array (implanted in the scala tympani of the cochlea) are the part of internal component of a cochlear implant , which is fitted inside the body.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Transmitter / transmitting coil is also the part of external component of a cochlear implant. Option: C. Microphone is present in the speech processor. Option: D. Speech processor is a part of the external component of a cochlear implant.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Bone-anchored hearing aid (BAHA) can be used in which of the following:", "options": [{"label": "A", "text": "A 40 year old male with bilateral profound hearing loss", "correct": false}, {"label": "B", "text": "A 6 year old male with bilateral hearing loss", "correct": false}, {"label": "C", "text": "A 7 year old male with bilateral microtia, canal atresia, and congenital hearing loss", "correct": true}, {"label": "D", "text": "A 50 year old male with bilateral acoustic neuroma, planned for surgery", "correct": false}], "correct_answer": "C. A 7 year old male with bilateral microtia, canal atresia, and congenital hearing loss", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>A 7 year old male with bilateral microtia, canal atresia, and congenital hearing loss A bone-anchored hearing aid is indicated in people with bilateral microtia, canal atresia, and congenital hearing loss unamenable to treatment .</p>\n<p><strong>Highyeild:</strong></p><p>BAHA : Bone anchored hearing aid is a type of hearing aid that is based on the principle of bone conduction. It uses a surgically implanted abutment to transmit sound by direct conduction through bone to the cochlea, bypassing the external auditory canal and middle ear. Bone-anchored hearing aid (BAHA).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. BAHA is indicated in unilateral hearing loss, not in bilateral hearing loss. Option: B. Same explanation as option A. Option: D. In bilateral acoustic neuroma also BAHA cannot be used as it is bilateral and retrocochlear.</p>\n<p><strong>Extraedge:</strong></p><p>BAHA has three components: (i) Titanium fixture (ii) Titanium abutment (iii) Sound processor. The titanium fixture is surgically embedded in the skull bone with an abutment exposed outside the skin. The titanium fixture bonds with the surrounding tissue in a process called osseointegration. The sound processor is attached to the abutment once osseointegration is complete which usually takes 2–6 months after implantation.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Cochlear implant is done in:", "options": [{"label": "A", "text": "Scala Vestibuli", "correct": false}, {"label": "B", "text": "Scala tympani", "correct": true}, {"label": "C", "text": "Cochlear Duct", "correct": false}, {"label": "D", "text": "Endolymphatic Duct", "correct": false}], "correct_answer": "B. Scala tympani", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Scala tympani Electrodes are implanted into the scala tympani via cochleostomy. This allows the electrodes to be in close proximity to the spiral ganglion cells and their dendrites.</p>\n<p><strong>Highyeild:</strong></p><p>Indications of a cochlear implant. Bilateral severe to profound sensorineural hearing loss. Little or no benefit from hearing aids. No medical contraindication for surgery. No retro cochlear</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Stimulator of the auditory brainstem implant is placed in?", "options": [{"label": "A", "text": "Recess of 4th ventricle", "correct": true}, {"label": "B", "text": "Scala Tympanum", "correct": false}, {"label": "C", "text": "Oval Window", "correct": false}, {"label": "D", "text": "EAC", "correct": false}], "correct_answer": "A. Recess of 4th ventricle", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Recess of 4th ventricle The stimulator of the auditory brainstem implant is placed in the recess of the IVth ventricle. Auditory brain stem implant is designed to stimulate the cochlear nuclear complex in the brainstem directly placing the implant in the lateral recess of the 4th ventricle.</p>\n<p><strong>Highyeild:</strong></p><p>Indications for Auditory Brainstem Implant : B/L acoustic neuroma as in Neurofibromatosis- 2 Absent cochlea Absent auditory nerve The stimulator of the auditory brainstem implant is placed in the recess of the IVth ventricle</p>\n<p><strong>Extraedge:</strong></p><p>Brainstem implant is similar to “Nucleus” multichannel cochlear implant except that the multielectrode array is attached to a Dacron mesh, which is placed on the brainstem. The receiver/stimulator has a removable magnet so that MRI can be safely performed in such cases if the need arises.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A child was treated for H. Influenza meningitis for 6 months. The most important investigation to be done before discharging the patient is:", "options": [{"label": "A", "text": "MRI", "correct": false}, {"label": "B", "text": "Brainstem evoked auditory response", "correct": true}, {"label": "C", "text": "Growth Screening Test", "correct": false}, {"label": "D", "text": "Psychotherapy", "correct": false}], "correct_answer": "B. Brainstem evoked auditory response", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Brainstem evoked auditory response Auditory brainstem response is used both as a screening test and as a definitive hearing assessment test in children. It is the most important investigation to be done before discharging the patient of h. Influenzae meningitis.</p>\n<p><strong>Highyeild:</strong></p><p>H. Influenza Meningitis It is frequent in children between the ages of 3 and 12 months. The residual auditory deficit is a common complication. Since residual auditory deficit is a common complication of H. influenza meningitis so audiological test to detect the deficit should be performed before discharging any patient suffering from H. influenza meningitis. In children, the best test to detect hearing loss is brainstem-evoked auditory response. “Auditory brainstem response is used both as a screening test and as a definitive hearing assessment test in children”. Wave I Distal part of CN VIII Wave II Proximal part of CN VIII near the brainstem Wave III Cochlear nucleus Wave IV Superior olivary complex Wave V Lateral lemniscus Waves VI and VII Inferior colliculus</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All Are True About Cochlear Implants Except:", "options": [{"label": "A", "text": "The minimum age of the implant is 1 year(9 months)", "correct": false}, {"label": "B", "text": "Indication- PTA of 70 Db or greater in both ears", "correct": false}, {"label": "C", "text": "Switch on is done after 3 week", "correct": false}, {"label": "D", "text": "MRI is having no role in pre-op assessment", "correct": true}], "correct_answer": "D. MRI is having no role in pre-op assessment", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>MRI is having no role in pre-op assessment Imaging of the temporal bone, cochlea, auditory nerve, and brain is carried out using CT and This is required to provide an image of the structure of the cochlea and help identify any anomalies or pathology that may complicate the implantation process .</p>\n<p><strong>Highyeild:</strong></p><p>Indications Bilateral severe to profound sensorineural hearing loss. Little or no benefit from hearing aids. No medical contraindication for surgery. No retro-cochlear disease.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Minimum age of implant is 9 months – 12 months. Option: B. B/L severe to profound SNHL (> 70db). Option: C . Activation or switch on of implant is done 3 – 4 weeks after implantation.</p>\n<p><strong>Extraedge:</strong></p><p>O UTCOMES OF C OCHLEAR I MPLANTATION . Factors that predict a successful clinical outcome are: Previous auditory experience (postlingual patients or prior use of hearing aids). Younger age at implantation (especially for prelingual children). Shorter duration of deafness. Neural plasticity within the auditory system.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 18 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 9</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Audiometry - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 9</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 9 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "In pure tone audiogram, the symbol X is used to mark:", "options": [{"label": "A", "text": "Air conduction in the right ear", "correct": false}, {"label": "B", "text": "Air conduction in the left ear", "correct": true}, {"label": "C", "text": "Bone conduction in the right ear", "correct": false}, {"label": "D", "text": "No change in air conduction in the right ear", "correct": false}], "correct_answer": "B. Air conduction in the left ear", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Air conduction in the left ear Symbol X is used for air conduction in the left ear.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are subjective tests for audiometry except:", "options": [{"label": "A", "text": "Tone Decay", "correct": false}, {"label": "B", "text": "Impedance Audiometry", "correct": true}, {"label": "C", "text": "Speech Audiometry", "correct": false}, {"label": "D", "text": "Pure Tone Audiometry", "correct": false}], "correct_answer": "B. Impedance Audiometry", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Impedance Audiometry Impedance audiometry is an objective hearing test.</p>\n<p><strong>Highyeild:</strong></p><p>IMPEDANCE AUDIOMETRY It is beneficial in children for assessing hearing loss. It consists of tympanometry and acoustic reflex. Tympanometry measures the middle ear's condition at the tympanic membrane's level. NOTE A tympanogram obtained using a 220 Hz probe may erroneously appear normal for infants and neonates. Therefore, a higher frequency probe tone (660 or 1000 Hz) must be used. (A) Impedance audiometry in progress. (B) Impedance audiometer.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Tone Decay is a subjective test. Option C. Speech Audiometry is a subjective test Option D. Pure Tone Audiometry is a subjective test.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Acoustic dip occurs at:", "options": [{"label": "A", "text": "2000 Hz", "correct": false}, {"label": "B", "text": "4000 Hz", "correct": true}, {"label": "C", "text": "500Hz", "correct": false}, {"label": "D", "text": "1500 Hz", "correct": false}], "correct_answer": "B. 4000 Hz", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>4000 Hz The acoustic dip is seen in pure tone audiometry due to noise trauma, typically at 4 kHz (4000 Hz).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "At which level sound is painful:", "options": [{"label": "A", "text": "100-120DB", "correct": true}, {"label": "B", "text": "80-85DB", "correct": false}, {"label": "C", "text": "60-65DB", "correct": false}, {"label": "D", "text": "20-25DB", "correct": false}], "correct_answer": "A. 100-120DB", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>100-120DB The sound is painful at around 130 dB . Of the following options, 100-120 d B would be the perfect choice for the above question.</p>\n<p><strong>Highyeild:</strong></p><p>SOUND INTENSITIES Whisper 30dB Normal conversation 60dB Shout 90dB Discomfort of ear 120 dB Pain in ear 130dB</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "High-frequency audiometry is used in:", "options": [{"label": "A", "text": "Otosclerosis", "correct": false}, {"label": "B", "text": "Ototoxicity", "correct": true}, {"label": "C", "text": "Non-Organic Hearing Loss", "correct": false}, {"label": "D", "text": "Meniere's Disease", "correct": false}], "correct_answer": "B. Ototoxicity", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ototoxicity Some environmental factors, such as ototoxic medication like aminoglycosides and noise exposure , appear more detrimental to high-frequency sensitivity than to mid or low frequencies . Therefore, high-frequency audiometry effectively monitors losses suspected to have been caused by these factors . It also effectively detects auditory sensitivity changes that occur with ageing.</p>\n<p><strong>Highyeild:</strong></p><p>Conventional audiometry tests frequencies between 0.25 kHz - 8 kHz, whereas high-frequency audiometry tests in the 8 kHz-20 kHz. Ototoxic drugs like aminoglycosides typically affect higher-frequency hearing first and progress to lower frequencies. Otoacoustic emissions (OAE) are more sensitive to detecting auditory dysfunction than high-frequency pure-tone audiometry. OAEs also have the advantage of being practical at the bedside and do not require a soundproof room. Distortion product OAEs are more sensitive than transient evoked OAEs for detecting early signs of ototoxicity.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "9 Month baby came to ENT OPD with c/o not responding to even the loudest sounds. The hearing assessment was done, which showed OAE waves were present, but on BERA, there were no waves. This condition is called as-.", "options": [{"label": "A", "text": "Presbycusis", "correct": false}, {"label": "B", "text": "Auditory Neuropathy", "correct": true}, {"label": "C", "text": "Ill Formed Cochlea", "correct": false}, {"label": "D", "text": "Low IQ", "correct": false}], "correct_answer": "B. Auditory Neuropathy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Auditory Neuropathy Auditory neuropathy is a neurologic disorder of cranial nerve VIII. Audiometric tests show : Pure tone – SNHL Impaired speech discrimination score Absent or abnormal auditory brainstem response But OAEs are normal</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Presbycusis is age-related hearing loss. It is not seen in infants. O ption C. In an Ill-formed cochlea, OAE will be abnormal. Option D. In low IQ, audiometric tests will be normal.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 6-year-old child presents with bilateral hearing difficulty for three months. Impedance audiometry shows a type B Curve. There is a bilateral conductive hearing deficit. Hearing loss is fluctuating in nature. There is no sign of infection. What is the diagnosis?", "options": [{"label": "A", "text": "SOM", "correct": true}, {"label": "B", "text": "Otosclerosis", "correct": false}, {"label": "C", "text": "Retracted tympanic membrane", "correct": false}, {"label": "D", "text": "none of the above", "correct": false}], "correct_answer": "A. SOM", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>SOM The given case scenario is talking about serous otitis media . It is a c ollection of sterile fluid in the Middle ear. In the tympanogram, there is a type B curve.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption B. In otosclerosis, there is type As type of tympanogram. Option C. There is a type C type of tympanogram in the retracted tympanic membrane.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 30-year-old female presented with hearing loss. The audiometry showed the following finding. The following operations are done in the case of this patient:", "options": [{"label": "A", "text": "Stapedectomy", "correct": false}, {"label": "B", "text": "Fenestration", "correct": false}, {"label": "C", "text": "Stapedotomy", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003348397-QTDE042010IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above Given audiogram points to the diagnosis of otosclerosis . It shows Carhart’s notch, which is characteristic of otosclerosis.</p>\n<p><strong>Highyeild:</strong></p><p>Role of surgery in a case of otosclerosis: Surgery forms the mainstay of management in a case of otosclerosis (Surgery of choice) ↓ Stapedectomy/stapedotomy (surgery of choice) Lemperts fenestration procedure ↓ Fenestration of the lateral semicircular canal is done. It is reserved for cases where the footplate cannot be mobilised during stapedectomy (Outdated nowadays) Stapes mobilisation ↓ It is done only in cases where the stapes footplate has partial ankylosis, although reankylosis tends to develop.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 55-year-old female came to the ENT clinic. You order an audiogram in a patient with a hearing impairment. What is your inference in this audiogram?", "options": [{"label": "A", "text": "Left side conductive deafness", "correct": false}, {"label": "B", "text": "Left side mixed hearing loss", "correct": true}, {"label": "C", "text": "Right side conductive deafness", "correct": false}, {"label": "D", "text": "Left side sensorineural deafness", "correct": false}], "correct_answer": "B. Left side mixed hearing loss", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003348503-QTDE042011IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Left side mixed hearing loss The given audiogram is inferred as a case of left-sided mixed hearing loss. Interpretation of the audiogram: > indicates BC (bone conduction), and the threshold is >25 dB. Hence BC is abnormal → sensorineural pathway is abnormal. X indicates AC (air conduction), and the threshold is >15 dB. Hence AC is abnormal. Both AC and BC are abnormal. It means sensorineural hearing loss. In addition, the air-bone gap is present, which implies conductive hearing loss. Hence interpreted as left mixed hearing loss.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 19 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 6</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">BERA & OAE - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 6</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 6 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "All Are False About Hearing Tests Except:", "options": [{"label": "A", "text": "OAE arises from inner hair cells", "correct": false}, {"label": "B", "text": "BERA is the screening test for hearing in children", "correct": false}, {"label": "C", "text": "Lower frequencies are localised towards the apex of the cochlea", "correct": true}, {"label": "D", "text": "OAE is not affected by middle ear pathology", "correct": false}], "correct_answer": "C. Lower frequencies are localised towards the apex of the cochlea", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lower frequencies are localised towards the apex of the cochlea In Cochlea, higher frequencies are represented in the basal turn of the cochlea and the progressively lower ones towards the apex . So statement C is true.</p>\n<p><strong>Highyeild:</strong></p><p>FREQUENCY LOCALIZATION IN COCHLEA Frequency localization in the cochlea. Higher frequencies are localized in the basal turn and then progressively decrease towards the apex.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. OAE arises from outer hair cells and not inner hair cells. Option B. BERA is used for auditory evaluation in children but not the 1° screening test. Option D. OAE is affected by middle ear pathology.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The most reliable wave in Bera is:", "options": [{"label": "A", "text": "Wave 2", "correct": false}, {"label": "B", "text": "Wave 3", "correct": false}, {"label": "C", "text": "Wave 4", "correct": false}, {"label": "D", "text": "Wave 5", "correct": true}], "correct_answer": "D. Wave 5", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Wave 5 Important waves in BERA are 1st, 3rd and 5t. Amongst these, 5th is the most important. Interaural wave five latency difference is the most important parameter .</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An incision in the Otitis media with effusion for grommet insertion is given at-", "options": [{"label": "A", "text": "Anteroinferior", "correct": true}, {"label": "B", "text": "Anterosuperior", "correct": false}, {"label": "C", "text": "Posterosuperior", "correct": false}, {"label": "D", "text": "Posteroinferior", "correct": false}], "correct_answer": "A. Anteroinferior", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Anteroinferior The grommet is inserted in the anteroinferior part of the tympanic membrane in otitis media with effusion .</p>\n<p><strong>Highyeild:</strong></p><p>Otitis media with effusion If myringotomy and aspiration combined with medical measures have not helped and fluid recurs, a grommet is inserted to continue aeration of the middle ear. It is left in place for weeks or months or till it is spontaneously extruded. Grommet in the tympanic membrane (A and B).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 9-month-old baby was brought to ENT OPD complaining of not responding even to the loudest sounds. The hearing assessment was done, which showed OAE waves was present but no wave on BERA. This condition can be:", "options": [{"label": "A", "text": "Absent Cochlea", "correct": false}, {"label": "B", "text": "Auditory Neuropathy", "correct": true}, {"label": "C", "text": "Malformed Cochlea", "correct": false}, {"label": "D", "text": "Low IQ", "correct": false}], "correct_answer": "B. Auditory Neuropathy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Auditory Neuropathy Auditory neuropathy is a neurologic disorder of cranial nerve VIII. Audiometric tests show : Pure tone – SNHL Impaired speech discrimination score Absent or abnormal auditory brainstem response But OAEs are normal</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Presbycusis is age-related hearing loss. It is not seen in infants. O ption C. In an ill-formed cochlea, OAE will be abnormal. Option D. In low IQ, audiometric tests will be normal.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "To distinguish between cochlear and post-cochlear damage test done is:", "options": [{"label": "A", "text": "Brainsterm evoked response audiometry", "correct": true}, {"label": "B", "text": "Impedance Audiometry", "correct": false}, {"label": "C", "text": "Pure Tone Audiometry", "correct": false}, {"label": "D", "text": "Auditory Cochlear Potential", "correct": false}], "correct_answer": "A. Brainsterm evoked response audiometry", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Brainsterm evoked response audiometry BERA testing objectively assesses the neural synchrony of the auditory system from the level of eight nerves to the midbrain. It is beneficial in distinguishing between cochlear pathology and retro cochlear pathology for SNHL. Cochlear SNHL - occurs due to damage to hair cells mainly. Retrocochlear SNHL- occurs due to a lesion of the VIIIth nerve or its central connection. Hence BERA can diagnose a retro cochlear pathology.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "True regarding this investigation is:", "options": [{"label": "A", "text": "It is an invasive procedure", "correct": false}, {"label": "B", "text": "Wave 4th is the most stable and reliable wave", "correct": false}, {"label": "C", "text": "Test of choice for neonatal hearing screening", "correct": false}, {"label": "D", "text": "Wave 1st originate from the distal part of eight nerve", "correct": true}], "correct_answer": "D. Wave 1st originate from the distal part of eight nerve", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003384863-QTDE043006IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Wave 1st originate from the distal part of eight nerve The given Image shows BERA – Brainstem evoked response audiometry. 1st part of the wave originates from the distal part of the 8th nerve.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - O ption A. BERA is a non-invasive procedure. O ption B. The first, third and fifth waves are the most stable and are used in measurements. Option C. These are used as screening procedures in infants.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 16 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 5</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">BPPV - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 5</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 5 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Tumarkin crisis occurs in which of the following diseases?", "options": [{"label": "A", "text": "Otospongiosis", "correct": false}, {"label": "B", "text": "Tympanosclerosis", "correct": false}, {"label": "C", "text": "Meniere Disease", "correct": true}, {"label": "D", "text": "CSOM", "correct": false}], "correct_answer": "C. Meniere Disease", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Meniere Disease Tumarkin crisis /Tumarkin's otolithic crisis or Drop attacks are seen in Meniere’s disease. Abrupt falling attacks of brief duration without loss of consciousness . It is due to an enlarged utricle due to excess endolymphatic drainage . An erroneous vertical gravity reference occurs due to the abrupt change in otolithic input. This generates inappropriate postural adjustments via the vestibulospinal pathway, which results in a fall.</p>\n<p><strong>Highyeild:</strong></p><p>MENIERE’S DISEASE/ ENDOLYMPHATIC HYDROPS It is a disease of membranous Labyrinth Causes: Increase in production of Endolymph, Decrease in drainage or Both seen in Middle-Aged Males (30- 50yrs) Unilateral Presentation Fluctuating tinnitus Fluctuating hearing loss (SNHL) Episodic vertigo, Nystagmus, Nausea Special features of Meniere’s Hennebert’s sign : False positive Fistula sign. Tulio’s Phenomenon : Loud noise Produces vertigo/ Nystagmus. Diplacusis : The same word heard 2 times in different frequencies in the same ear. Recruitment.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient has U/L progressive and fluctuant SNHL, tinnitus, and Ear Fullness with displacusis. What is the diagnosis based on the symptoms?", "options": [{"label": "A", "text": "Otospongiosis", "correct": false}, {"label": "B", "text": "Tympanosclerosis", "correct": false}, {"label": "C", "text": "Meniere Disease", "correct": true}, {"label": "D", "text": "CSOM", "correct": false}], "correct_answer": "C. Meniere Disease", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Meniere Disease History findings described above are suggestive of Meniere’s disease.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Otosclerosis, more aptly called otospongiosis, is a primary disease of the bony labyrinth. In this, one or more foci of irregularly laid spongy bone replace part of the normally dense enchondral layer of the bony otic capsule. Option B- Tympanosclerosis is hyalinization and later calcification in the fibrous layer of the tympanic membrane. It appears as a chalky white plaque. Mostly, it remains asymptomatic. It is frequently seen in cases of serous otitis media as a complication of a ventilation tube. Option D- Chronic suppurative otitis media (CSOM) is a long-standing infection of a part or whole of the middle ear cleft characterized by ear discharge and a permanent perforation.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45-year-old patient of Meniere's disease has failed all medical treatments but still retains serviceable hearing. All of the following treatments can be considered except:", "options": [{"label": "A", "text": "Endolymphatic Sac Decompression", "correct": false}, {"label": "B", "text": "Intratympanic Gentamicin", "correct": false}, {"label": "C", "text": "Vestibular Nerve Section", "correct": false}, {"label": "D", "text": "Labyrinthectomy", "correct": true}], "correct_answer": "D. Labyrinthectomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Labyrinthectomy Since the hearing of the patient is serviceable as present, those procedures should be considered which retain useful hearing such as in (a), (b) and (c). Labyrinthectomy will totally destroy the hearing and is not suitable for this patient.</p>\n<p><strong>Highyeild:</strong></p><p>Conservative procedures done for Ménière's include: Decompression of endolymphatic sac Endolymphatic shunt operation Sacculotomy (Fick's operation) Section of the vestibular nerve Ultrasonic destruction of vestibular labyrinth. These are used when vertigo is disabling, but hearing is still useful and needs to be preserved.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 49-year-old man presented to the ENT surgeon with a history of vertigo whenever he moves sideways on the bed while lying supine. He would feel as if the room is spinning and would feel nauseous. The symptoms subside after a few minutes but return when he moves his head. Which of the following tests is most appropriate to diagnose this condition?", "options": [{"label": "A", "text": "Dix-Hallpike's Manoeuvre", "correct": true}, {"label": "B", "text": "Carotid Doppler", "correct": false}, {"label": "C", "text": "CT Scan", "correct": false}, {"label": "D", "text": "Epley's Manoeuvre", "correct": false}], "correct_answer": "A. Dix-Hallpike's Manoeuvre", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Dix-Hallpike's Manoeuvre The given clinical scenario is suggestive of benign paroxysmal positional vertigo (BPPV) . The diagnostic maneuver used in BPPV is the Dix-Hallpike maneuver , the gold standard for diagnosing BPPV. The therapeutic maneuver is Epley's maneuver.</p>\n<p><strong>Highyeild:</strong></p><p>Dix-Hallpike maneuver The maneuver starts with the patient in a sitting position. Their head is turned 45 degrees towards the ear to be tested. Then the examiner quickly brings the patient to a supine position with their head 30 degrees below the horizontal. The 45-degree head rotation is maintained during the procedure. Now the examiner observes the patient for nystagmus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient with blood pressure 120/90 mm of Hg comes to the hospital and says he feels like the room is spinning when he gets up from lying down or turning his head. He has no history of loss of consciousness. Which of the following could be the probable diagnosis?", "options": [{"label": "A", "text": "Benign paroxysmal positional vertigo (BPPV)", "correct": true}, {"label": "B", "text": "Meniere's Disease", "correct": false}, {"label": "C", "text": "Labyrinthitis", "correct": false}, {"label": "D", "text": "Syncope", "correct": false}], "correct_answer": "A. Benign paroxysmal positional vertigo (BPPV)", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Benign paroxysmal positional vertigo (BPPV) Based on the symptoms, the most probable diagnosis is Benign paroxysmal positional vertigo (BPPV) .</p>\n<p><strong>Highyeild:</strong></p><p>BPPV BPPV is one of the common causes of recurrent vertigo, and the episodes are brief ( < 1min) and are always provoked by changes in head position. The attacks are caused by free-floating calcium carbonate crystals usually dislodged from the posterior canal. Epley's maneuver is performed to reposition the crystals into the macula from the semicircular canals.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. In Meniere’s disease, there is Fluctuating tinnitus, Fluctuating hearing loss (SNHL), Episodic vertigo, Nystagmus, and Nausea. Option C. Labyrinthitis is inflammation of the labyrinth. Option D. In syncope there is a loss of consciousness.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 15 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 11</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Cholesteatoma & Tuberculosis of Ear - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 11</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 11 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A teenage girl was brought into the OPD complaining of mild left-sided ear discharge that has persisted for the last few weeks. She has completed two courses of antibiotics that were prescribed during her previous visits to her family physician. She also complains of hearing loss on the left side. On examination, she is afebrile and with vitals in the normal range. Otoscopy reveals an intact left tympanic membrane with peripheral granulation and some skin debris. The patient should be evaluated for which of the following?", "options": [{"label": "A", "text": "Cholesteatoma", "correct": true}, {"label": "B", "text": "Craniopharyngioma", "correct": false}, {"label": "C", "text": "Otosclerosis", "correct": false}, {"label": "D", "text": "Meniere's Disease", "correct": false}], "correct_answer": "A. Cholesteatoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cholesteatoma This patient should undergo further evaluation for a possible cholesteatoma . The diagnosis should be suspected in any patient with continued ear drainage for several weeks despite appropriate antibiotic therapy. Chronic middle ear disease leads to the formation of a retraction pocket in the tympanic membrane, which can fill with granulation tissue and skin debris , as seen in this patient.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Craniopharyngioma is a tumour that can occur in children. However, it is derived from Rathke's pouch, which is located in the suprasellar space. Option: C. Otosclerosis is a condition in which there is a bony overgrowth of the stapes footplate that results in conductive hearing loss. Ear drainage would not be present. Option: D. Meniere's disease is a condition associated with an accumulation of fluid in the inner ear that leads to hearing loss, vertigo, and tinnitus. The presence of ear drainage and the lack of vertigo make Meniere's disease unlikely in this case.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Cholesteatoma is commonly caused by:", "options": [{"label": "A", "text": "Attico-Antral Perforation", "correct": true}, {"label": "B", "text": "Tubotympanic Disease", "correct": false}, {"label": "C", "text": "Central perforation of tympanic membrane", "correct": false}, {"label": "D", "text": "Meniere's Disease", "correct": false}], "correct_answer": "A. Attico-Antral Perforation", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Attico-Antral Perforation The attico-antral disease is characterized by cholesteatoma which erodes the bone.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B- In tubotympanic disease, no cholesteatoma is formed. Option C- Central perforation of the tympanic membrane is seen in tubotympanic disease in which no cholesteatoma is seen. Option D- Ménière’s disease, also called endolymphatic hydrops , is a disorder of the inner ear where the endolymphatic system is distended with endolymph.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Most accepted theory for the formation of cholesteatoma:", "options": [{"label": "A", "text": "Ruedi’s theory", "correct": false}, {"label": "B", "text": "Habermann theory", "correct": false}, {"label": "C", "text": "Sade’s theory", "correct": false}, {"label": "D", "text": "Retraction Pocket", "correct": true}], "correct_answer": "D. Retraction Pocket", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Retraction Pocket The most commonly accepted theory for the formation of cholesteatoma is the formation of a retraction pocket . According to this theory, chronic negative middle ear pressure leads to retractions of the structurally weakest area of the tympanic membrane, the pars flaccida. Once the retractions form, the normal migratory pattern of the squamous epithelium is disrupted, resulting in the accumulation of keratin debris in the cholesteatoma sac.</p>\n<p><strong>Highyeild:</strong></p><p>GRADING OF RETRACTION POCKET Grade I: Retraction pocket present but not touching malleus Grade II: Retraction pocket present, touches malleus but no bony erosions Grade III: Retraction pocket present, partial bony erosions (scutum) Grade IV: Retraction pocket present. Total bony erosions (Scutum)</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- In Ruedi’s theory the basal cells of a germinal layer of skin proliferate under the influence of infection and lay down keratinizing squamous epithelium. Option B- In Habermann's theory The epithelium from the meatus or outer drum surface grows into the middle ear through a pre-existing perforation, especially of the marginal type where part of the annulus tympanicus has already been destroyed. Option C- In Sade’s theory Middle ear mucosa, like respiratory mucosa elsewhere, undergoes metaplasia due to repeated infections and transforms into squamous epithelium.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "True about cholesteatoma is/are:", "options": [{"label": "A", "text": "It is a benign tumour", "correct": false}, {"label": "B", "text": "Metastasizes to Lymph node", "correct": false}, {"label": "C", "text": "Contains Cholesterol", "correct": false}, {"label": "D", "text": "Erodes The Bone", "correct": true}], "correct_answer": "D. Erodes The Bone", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Erodes The Bone Cholesteatoma has the property to destroy bones (due to the various enzymes released by it and not by pressure necrosis ).</p>\n<p><strong>Highyeild:</strong></p><p>Cholesteatoma has 2 parts: Matrix: made of keratinising squamous epithelium. Hence also k/a epidermosis or keratoma Central white mass – made of keratin debris.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- The term cholesteatoma literally means “Skin in the wrong place.” It is a misnomer because neither it contains cholesterol Crystals nor it is a tumour. Option B- The term cholesteatoma literally means “Skin in the wrong place.” It is a misnomer because neither it contains cholesterol Crystals nor it is a tumour. So it doesn’t metastasize to lymph nodes. Option C- The term cholesteatoma literally means “Skin in the wrong place.” It is a misnomer because neither it contains cholesterol Crystals nor it is a tumour.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Cholesteatoma commonly perforates:", "options": [{"label": "A", "text": "Lateral Semicircular Canal", "correct": true}, {"label": "B", "text": "Sup. Semicircular Canal", "correct": false}, {"label": "C", "text": "Promontory", "correct": false}, {"label": "D", "text": "Oval Window", "correct": false}], "correct_answer": "A. Lateral Semicircular Canal", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lateral Semicircular Canal Cholesteatoma has the property to destroy the bone by virtue of the various enzymes released by it. Out of the above options, cholesteatoma commonly erodes lateral/horizontal semicircular canal.</p>\n<p><strong>Highyeild:</strong></p><p>Structures immediately at risk of erosion by cholesteatoma are: The long process of incus. Fallopian canal containing facial nerve. Horizontal/lateral semicircular canal.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The most difficult site to remove cholesteatoma in sinus tympani is related with:", "options": [{"label": "A", "text": "Anterior Facial Ridge", "correct": false}, {"label": "B", "text": "Posterior Facial Ridge", "correct": true}, {"label": "C", "text": "Epitympanum", "correct": false}, {"label": "D", "text": "Hypotympanum", "correct": false}], "correct_answer": "B. Posterior Facial Ridge", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Posterior Facial Ridge The sinus tympani (Posterior facial ridge) is the posterior extension of the mesotympanum and lies deep to both the promontory and facial nerve. The medial wall of the sinus tympani becomes continuous with the posterior portion of the medial wall of the tympanic cavity. This is the worst region for access because it is above the pyramid, posterior to intact stapes and medial to the facial nerve. A retro facial approach via mastoid is not possible because the posterior semicircular canal blocks the access.</p>\n<p><strong>Highyeild:</strong></p><p>SINUS TYMPANI</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 13-year-old male presented with a six-month history of non-foul smelling ear discharge the treatment includes all except:", "options": [{"label": "A", "text": "Topical Antibiotics", "correct": false}, {"label": "B", "text": "Tympanoplasty", "correct": false}, {"label": "C", "text": "Mastoidectomy", "correct": true}, {"label": "D", "text": "Systemic Antibiotics", "correct": false}], "correct_answer": "C. Mastoidectomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mastoidectomy Non-foul smelling discharge of 6 months duration suggests that the above case is of chronic otitis media of safe type hence the management is ear toileting, local and systemic antibiotics. Once the ear is dry for six weeks without antibiotics surgical repair by tympanoplasty.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Antibiotic ear drops containing neomycin, polymyxin, chloromycetin or gentamicin are used. Option B- Once the ear is dry, myringoplasty with or without ossicular reconstruction can be done to restore hearing. Option D- systemic antibiotics are useful in acute exacerbation of chronically infected ears, otherwise, the role of systemic antibiotics in the treatment of CSOM is limited.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "True about tubercular otitis media are all except:", "options": [{"label": "A", "text": "Spreads through Eustachian tube", "correct": false}, {"label": "B", "text": "Causes painless ear discharge", "correct": false}, {"label": "C", "text": "May cause multiple perforations", "correct": false}, {"label": "D", "text": "The process is very rapid", "correct": true}], "correct_answer": "D. The process is very rapid", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>The process is very rapid Pathology of tuberculous otitis media - The process is slow and insidious.</p>\n<p><strong>Highyeild:</strong></p><p>CLINICAL FEATURES P AINLESS E AR D ISCHARGE - Earache is characteristically absent in cases of tubercular otitis media. Discharge is often foul-smelling because of underlying bone destruction. P ERFORATION - Multiple perforations, two or three in number, are seen in pars tensa and form a classical sign of disease. H EARING L OSS - There is severe hearing loss, out of proportion to symptoms. Mostly conductive, it may have a sensorineural component due to the involvement of the labyrinth. F ACIAL P ARALYSIS - It is a common complication and may come unexpectedly. This may be the presenting feature in a child.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- In most cases, infection is secondary to pulmonary tuberculosis; infection reaches the middle ear through the Eustachian tube Option B- Earache is characteristically absent in cases of tubercular otitis media. Option C- Multiple perforations, two or three in number, are seen in pars tensa and form a classical sign of disease.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You see a 24-year-old male patient, Vikas, in the ENT OPD with a history of repeated episodes of ear discharge that was painless but foul-smelling for the past year. He also has a loss of hearing on that side. He had undergone surgery on the same ear 2 years back. What is the type of cholesteatoma seen here?", "options": [{"label": "A", "text": "Primary acquired cholesteatoma", "correct": false}, {"label": "B", "text": "Tertiary acquired cholesteatoma", "correct": false}, {"label": "C", "text": "Secondary acquired cholesteatoma", "correct": true}, {"label": "D", "text": "Congenital Cholesteatoma", "correct": false}], "correct_answer": "C. Secondary acquired cholesteatoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Secondary acquired cholesteatoma The history of repeated middle ear inflammation in the given clinical scenario suggests a diagnosis of secondary acquired cholesteatoma. Secondary acquired cholesteatoma occurs when there is already a pre - existing perforation in pars tensa . Middle ear mucosa undergoes metaplasia due to repeated infections of the middle ear through a pre-existing perforation.</p>\n<p><strong>Highyeild:</strong></p><p>CHOLESTEATOMA</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Primary acquired cholesteatoma presents with no history of previous otitis media or a pre-existing perforation. It is associated with a defect in pars flaccida. Option B- Tertiary acquired cholesteatoma is defined as that which occurs behind a normal-appearing tympanic membrane as the result of implantation or an antecedent middle ear inflammation. Option D- Congenital cholesteatoma is that which has its nidus of trapped squamous epithelium present at birth.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 30-year-old patient came to you in the ENT department. The patient was found to have scanty, foul smelling painless discharge from the ear is a characteristic feature of which of the following lesions:", "options": [{"label": "A", "text": "Cholesteatoma", "correct": true}, {"label": "B", "text": "Asom", "correct": false}, {"label": "C", "text": "Central Perforation", "correct": false}, {"label": "D", "text": "Otitis Externa", "correct": false}], "correct_answer": "A. Cholesteatoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cholesteatoma In cholesteatoma the ear discharge is due to bone erosion, hence it is scanty and foul-smelling . It is painless.</p>\n<p><strong>Highyeild:</strong></p><p>Bone destruction by cholesteatoma has been attributed to various enzymes liberated by osteoclasts and mononuclear inflammatory cells - Collagenase acid phosphatase and proteolytic enzymes. Cholesteatoma can cause the destruction of ear ossicles, erosion of the bony labyrinth, the canal of the facial nerve, and tegmen tympani leading to several complications.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B- In ASOM following rupture of TM, there is relief of pain and the ear discharge is profuse, mucopurulent and non-foul smelling. Option C- In central perforation, i.e. safe CSOM the ear discharge is painless, on and off and is mucopurulent, non-foul smelling and moderate in amount. Option D- In Otitis externa, e,g. furuncle, the ear discharge is only purulent and characteristically lacks the mucoid component which indicates that it is not coming from the middle ear. It is associated with pain.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 50-year-old male came in with painless foul-smelling ear discharge. The patient denies any history of trauma. He does recall the presence of a chronic ear infection until last week. Regarding this patient, which of the following is true?", "options": [{"label": "A", "text": "It consists of squamous epithelium.", "correct": true}, {"label": "B", "text": "It is a malignant tumour.", "correct": false}, {"label": "C", "text": "It should be left untreated.", "correct": false}, {"label": "D", "text": "It may metastasize to distant sites.", "correct": false}], "correct_answer": "A. It consists of squamous epithelium.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>It consists of squamous epithelium. The medical history and case findings point towards the diagnosis of cholesteatoma. Cholesteatoma is a destructive and expanding growth consisting of keratinizing squamous epithelium in the middle ear and/or mastoid process.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option B - The term cholesteatoma literally means “Skin in the wrong place.” It is a misnomer because neither it contains cholesterol Crystals nor it is a tumour. Option C- It must be treated surgically unless the patient refuses the surgery or is elderly with significant medical problems. Option D - The term cholesteatoma literally means “Skin in the wrong place.” It is a misnomer because neither it contains cholesterol Crystals nor it is a tumour. So it doesn't metastasize to lymph nodes.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 21 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 7</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Chronic Laryngitis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 7</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 7 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "All Are True About Pachydermia Laryngis Except:", "options": [{"label": "A", "text": "Usually anterior part is involved", "correct": true}, {"label": "B", "text": "M.C Etiology is LERD", "correct": false}, {"label": "C", "text": "Treatment is medical and surgery", "correct": false}, {"label": "D", "text": "M.C symptom is hoarseness of voice", "correct": false}], "correct_answer": "A. Usually anterior part is involved", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Usually anterior part is involved In Pachydermia laryngis posterior part of the larynx is involved , not the anterior region.</p>\n<p><strong>Highyeild:</strong></p><p>Pachydermia laryngis It affects the posterior part of the larynx and is a form of chronic hypertrophic laryngitis. Most commonly patient presents with Hoarseness of voice or husky voice and irritation in the throat The condition is bilateral and symmetrical. Etiology is uncertain but mostly seen in LERD, i.e. laryngoesophageal reflux disease, people who smoke and alcohol. Rx: Removal of granulation tissue under an operating microscope, control of reflux and speech therapy.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. Etiology is uncertain but mostly seen in LERD, i.e. laryngoesophageal reflux disease. Option C. Treatment is removing granulation tissue under an operating microscope, control of reflux and speech therapy. So both medical and surgical treatment is done. Option D. Most commonly patient presents with Hoarseness of voice or husky voice and irritation in the throat.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Tubercular laryngitis primarily affects?", "options": [{"label": "A", "text": "Anterior Commissure", "correct": false}, {"label": "B", "text": "Posterior commissure of the larynx", "correct": true}, {"label": "C", "text": "Anywhere within the larynx", "correct": false}, {"label": "D", "text": "Superior surface of the larynx", "correct": false}], "correct_answer": "B. Posterior commissure of the larynx", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Posterior commissure of the larynx Tuberculosis affects the posterior part of the larynx more than the anterior part.</p>\n<p><strong>Highyeild:</strong></p><p>Tubercular laryngitis is almost always secondary to pulmonary tuberculosis. Parts affected are Inter arytenoid fold > Ventricular bands > Vocal cords > Epiglottis. On laryngeal examination The first sign is hyperaemia of the vocal cord to its whole extent or confined to the posterior part with impairment of adduction. Swelling in the inter arytenoid region gives a mamillated appearance. Ulceration of vocal cord giving mouse nibbled appearance. Treatment is the same as pulmonary tuberculosis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are features on indirect laryngoscopy of tuberculous laryngitis except:", "options": [{"label": "A", "text": "Anterior part is most commonly involved", "correct": true}, {"label": "B", "text": "Swelling of aryepiglottic folds.", "correct": false}, {"label": "C", "text": "Congestion and ulceration over vocal cords", "correct": false}, {"label": "D", "text": "Pseudoedema of epiglottis and arytenoids", "correct": false}], "correct_answer": "A. Anterior part is most commonly involved", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Anterior part is most commonly involved The disease affects the posterior part of the larynx more than the anterior . Parts affected are – a. Interarytenoid fold, b. Ventricular bands, c. Vocal cords , d. Epiglottis in that order.</p>\n<p><strong>Highyeild:</strong></p><p>Tuberculous laryngitis It is mostly 2° to pulmonary tuberculosis, primarily affecting males in middle age. The disease affects the posterior part of the larynx more than the anterior. Parts affected are – a. Interarytenoid fold, b. Ventricular bands, c. Vocal cords, d. Epiglottis in that order. Laryngeal examination shows : Hyperemia of the vocal cord in its whole or only posterior part with impairment of adduction is the first sign. The swelling of inter arytenoid region gives it a mamillated appearance. Ulceration of the vocal cord gives it a mouse-nibbled appearance. Superficial ragged ulceration on the arytenoids and inter arytenoid region. Granulation tissue in the inter arytenoid region or vocal process of arytenoid. Pseudoedema of epiglottis (turban epiglottis). Swelling of ventricular bands and Aryepiglottic folds. The marked pallor of the surrounding mucosa.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. Swelling of ventricular bands and Aryepiglottic folds are seen on laryngeal examination. Option C. Ulceration of the vocal cord giving it a mouse-nibbled appearance, is also seen on laryngeal examination. Option D. Pseudoedema of epiglottis and arytenoids is also seen on laryngeal examination.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 1 -year-old infant has biphasic stridor, barking cough and difficulty in breathing for 3-4 days. He has a high-grade fever, and leukocyte count is increased. Which of the following would not be a true statement regarding the child's clinical condition?", "options": [{"label": "A", "text": "It is more common in boys than in girls", "correct": false}, {"label": "B", "text": "Subglottic area is the common site of involvement", "correct": false}, {"label": "C", "text": "Antibiotics are the mainstay of treatment", "correct": true}, {"label": "D", "text": "Narrowing of subglottic space with ballooning of hypopharynx is seen", "correct": false}], "correct_answer": "C. Antibiotics are the mainstay of treatment", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Antibiotics are the mainstay of treatment According to the above history, acute laryngotracheobronchitis/croup is diagnosed. Since croup is chiefly viral in etiology , antibiotics play no role . So option C is incorrect.</p>\n<p><strong>Highyeild:</strong></p><p>Acute Laryngotracheobronchitis/Croup Cause- parainfluenza virus. Age- 6 months to 3 years. Presentation- low-grade fever, croupy voice. The most important investigation- is X-ray ( steeple sign/pencil tip sign). TREATMENT Steroid Settle down oedema and prevents sudden worsening of the condition. Nebulisation with Adrenaline or Epinephrine Antibiotics to prevent supra-added bacterial infection</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. It is more common in boys than in girls, is a true statement. Option B. The loose areolar tissue in the subglottic region swells up and causes respiratory obstruction and stridor. So the subglottic area is the common site of involvement. Option D. Narrowing of subglottic space with ballooning of hypopharynx is seen is a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A middle-aged male comes to the outpatient department (OPD) with the only complaint of hoarseness of voice for the past two years. He has been a chronic smoker for 30 years. A reddish area of mucosal irregularity overlying a portion of both cords was seen on examination. Management would include all except:", "options": [{"label": "A", "text": "Cessation of smoking", "correct": false}, {"label": "B", "text": "Bilateral Cordectomy", "correct": true}, {"label": "C", "text": "Micro laryngeal surgery for biopsy", "correct": false}, {"label": "D", "text": "Regular Follow-Up", "correct": false}], "correct_answer": "B. Bilateral Cordectomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Bilateral Cordectomy Middle-aged man + Chronic smoking + Hoarseness of voice + Bilateral reddish area of mucosal irregularity on cords. All these indicate that either it is pachydermia laryngitis or it can be early carcinoma . In both these conditions, cordectomy is not required.</p>\n<p><strong>Highyeild:</strong></p><p>Pachydermia laryngis It affects the posterior part of the larynx and is a form of chronic hypertrophic laryngitis. Most commonly patient presents with Hoarseness of voice or husky voice and irritation in the throat The condition is bilateral and symmetrical. Etiology is uncertain but mostly seen in LERD, i.e. laryngoesophageal reflux disease, people who smoke and alcohol. Rx: Removal of granulation tissue under an operating microscope, control of reflux and speech therapy.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. In either condition, smoking is a causative factor and should be stopped. Option C. Both the conditions can be distinguished by biopsy only, so option “c” is correct. Option D. Regular follow-up is a must in either of the conditions.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Mouse-nibbled ulceration of the vocal cord is seen in:", "options": [{"label": "A", "text": "Tb", "correct": true}, {"label": "B", "text": "Syphilis", "correct": false}, {"label": "C", "text": "Cancer", "correct": false}, {"label": "D", "text": "Papilloma", "correct": false}], "correct_answer": "A. Tb", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tb Mouse-nibbled ulceration of vocal cords is seen in tuberculosis of the larynx.</p>\n<p><strong>Highyeild:</strong></p><p>Tuberculous laryngitis It is mostly 2° to pulmonary tuberculosis, primarily affecting males in middle age. The disease affects the posterior part of the larynx more than the anterior. Parts affected are – a. Interarytenoid fold, b. Ventricular bands, c. Vocal cords, d. Epiglottis in that order. Laryngeal examination shows : Hyperemia of the vocal cord in its whole or only posterior part with impairment of adduction is the first sign. Swelling of the inter arytenoid region gives it a mamillated appearance. Ulceration of the vocal cord gives it a mouse-nibbled appearance. Superficial ragged ulceration on the arytenoids and inter arytenoid region. Granulation tissue in the inter arytenoid region or vocal process of arytenoid. Pseudoedema of epiglottis (turban epiglottis). Swelling of ventricular bands and Aryepiglottic folds. The marked pallor of the surrounding mucosa.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 6-year-old child came with c/o breathlessness, odynophagia, stridor and high fever. The child looked toxic and sat upright with a hyperextended neck. Which of the following investigations is not preferred in this case?", "options": [{"label": "A", "text": "X-Ray", "correct": false}, {"label": "B", "text": "Throat Swab", "correct": false}, {"label": "C", "text": "Indirect Laryngoscopy", "correct": true}, {"label": "D", "text": "Blood Culture", "correct": false}], "correct_answer": "C. Indirect Laryngoscopy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Indirect Laryngoscopy A child sitting upright with a hyperextended neck is called a tripod Breathlessness, odynophagia, stridor, high fever, looking toxic and sitting in a tripod position all indicate acute epiglottitis. Indirect laryngoscopy is avoided for fear of precipitating complete obstruction. It is better done in OT, where facilities for intubation are available.</p>\n<p><strong>Highyeild:</strong></p><p>Acute Epiglottitis Age group- 3 to 7 years. Presentation- high-grade fever Dyspnea ( sits in tripod position). Dysphagia(drooling of saliva). Laryngoscopy contraindicated. On X-ray- the tumb sign Drug of choice - cephalosporins</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. X-ray shows a thumb sign. Option B. Throat swab can be used to see H. Influenza type B is the most common organism causing this condition. Option D. Blood culture can be used to see H. Influenza type B is the most common organism causing this condition.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 17 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 7</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Complications of Sinusitis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 7</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 7 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "An adolescent girl diagnosed with sphenoid sinusitis was brought to the hospital with a fever and headache complaints. Would the headache most likely be localized?", "options": [{"label": "A", "text": "Frontal", "correct": false}, {"label": "B", "text": "Occiput", "correct": true}, {"label": "C", "text": "Temporal Region", "correct": false}, {"label": "D", "text": "Root of the nose.", "correct": false}], "correct_answer": "B. Occiput", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Occiput Headache in sphenoid sinusitis is localized to the occiput or vertex.</p>\n<p><strong>Highyeild:</strong></p><p>SPHENOID SINUSITIS Isolated involvement of sphenoid sinus is rare. It is often a part of pansinusitis or is associated with infection of the posterior ethmoid sinuses. CLINICAL FEATURES Headache. It is usually localized to the occiput or vertex. Pain may also be referred to the mastoid region. Postnasal discharge - It can only be seen on posterior rhinoscopy. A streak of pus may be seen on the nasopharynx's roof and the rear wall or above the middle turbinate's hind end. DIFFERENTIAL DIAGNOSIS Mucocele of the sphenoid sinus or its neoplasms may clinically simulate features of acute infection of the sphenoid sinus. It should permanently be excluded in any case of isolated sphenoid sinus involvement. TREATMENT Treatment is the same as for acute infection of other sinuses.</p>\n<p><strong>Extraedge:</strong></p><p>Frontal headache is seen in maxillary and frontal sinusitis. Headache in frontal sinusitis has characteristic periodicity.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient with sinus infection develops chemosis, B/L proptosis, and fever; the diagnosis goes in favor of:", "options": [{"label": "A", "text": "Lateral sinus thrombosis", "correct": false}, {"label": "B", "text": "Frontal lobe abscess", "correct": false}, {"label": "C", "text": "Cavernous sinus thrombosis", "correct": true}, {"label": "D", "text": "Meningitis", "correct": false}], "correct_answer": "C. Cavernous sinus thrombosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cavernous sinus thrombosis B/L proptosis, fever, and chemosis point towards cavernous sinus thrombosis.</p>\n<p><strong>Highyeild:</strong></p><p>Cavernous sinus thrombosis Infection of paranasal sinuses, particularly those of ethmoid and sphenoid, and less commonly, the frontal and orbital complications from these sinus infections can cause thrombophlebitis of the cavernous sinus. The valveless nature of the veins connecting the cavernous sinus causes easy spread of infection. The onset of cavernous sinus thrombophlebitis is abrupt, with chills and rigors. Eyelids get swollen with chemosis and proptosis of the Cranial nerves III, IV, and VI related to the sinus are involved individually and sequentially, causing total ophthalmoplegia. The pupil becomes dilated and fixed, and the optic disc shows congestion and edema with a diminution of vision. Sensation in the distribution of V1 (ophthalmic division of CN V) is diminished. CSF is usually normal. The condition needs to be differentiated from orbital cellulitis Treatment - v. Antibiotics and attention to the focus of infection, drainage of infected ethmoid or sphenoid sinus.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Lateral sinus thrombosis is an inflammation of the inner wall of the lateral venous sinus with the formation of an intrasinus thrombus. It occurs as a complication of acute coalescent mastoiditis, masked mastoiditis, or chronic middle ear suppuration and cholesteatoma. Option B. In Frontal lobe abscess, there is significant headache, mental status deterioration, etc. Option D. In meningitis, significant neck rigidity will be present.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Most common site for osteoma is:", "options": [{"label": "A", "text": "Maxillary Sinus", "correct": false}, {"label": "B", "text": "Ethmoid Sinus", "correct": false}, {"label": "C", "text": "Frontal Sinus", "correct": true}, {"label": "D", "text": "Sphenoid Sinus", "correct": false}], "correct_answer": "C. Frontal Sinus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Frontal Sinus The frontal sinus is the most frequent location of osteoma, followed by the ethmoid, maxillary, and sphenoid sinus, respectively.</p>\n<p><strong>Highyeild:</strong></p><p>OSTEOMA Age of presentation = second to fifth decade. Male–female ratio – 3:1. Presentation: Generally, they are an incidental finding in radiography It may produce symptoms like – Visual impairment Intracranial neurological complications like meningitis or pneumocephalus with seizure. They may remain asymptomatic, being discovered incidentally on X-rays. Treatment is indicated when they become symptomatic, obstructing the sinus ostium, formation of mucocele, and pressure symptoms due to their growth in the orbit, nose, or skull. A frontoethmoidal osteoma with invasion of the orbit (arrow). Osteoma right frontal sinus (arrow).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Maxillary Sinus is the third most common site for osteoma. Option B. Ethmoid Sinus is the second most common site. Option D. Sphenoid Sinus is the minor common site.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 2-year-old child with purulent nasal discharge, fever, and pain for the last two months. His fever is 102-103°C, and his leucocyte count is 12000 cu/mm. X-ray PNS showed opacification of left ethmoidal air cells. The culture of the eye discharge was negative. Which of the following would be the most helpful step in evaluating this patient?", "options": [{"label": "A", "text": "CT scan", "correct": true}, {"label": "B", "text": "Urine Culture", "correct": false}, {"label": "C", "text": "Blood culture", "correct": false}, {"label": "D", "text": "Repeat culture of the eye discharge", "correct": false}], "correct_answer": "A. CT scan", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>CT scan The child presents with fever and purulent nasal discharge with X-ray PNS showing opacification of ethmoidal sinus , i.e., probably the child is having chronic sinusitis (as it is present for the past two months) with an acute exacerbation. Now the most dreaded Complication of ethmoidal sinusitis is an orbital complication. Orbital complication – most complications follow ethmoid infection as they are separated from the orbit only by a thin lamina of bone – lamina papyracea. Infection travels from these sinuses either by ostitis or a thrombophlebitis process of ethmoidal veins. To check this CT scan is the most helpful step in evaluating this patient.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption B. Urine Culture has no role in this patient. O ption D. Blood culture also has no position in diagnosing orbital complications. Option D. Repeat culture of the eye discharge also has no significant role.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Cavernous sinus thrombosis following sinusitis results in all of the following signs except:", "options": [{"label": "A", "text": "Constricted pupil in response to light", "correct": true}, {"label": "B", "text": "Engorgement of retinal veins upon ophthalmoscopic examination", "correct": false}, {"label": "C", "text": "Ptosis of the eyelid", "correct": false}, {"label": "D", "text": "Ophthalmoplegia.", "correct": false}], "correct_answer": "A. Constricted pupil in response to light", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Constricted pupil in response to light Pupils are fixed and dilated (not constricted) due to the involvement of the III nerve and sympathetic plexus in cavernous sinus thrombosis.</p>\n<p><strong>Highyeild:</strong></p><p>Cavernous sinus thrombosis Infection of paranasal sinuses, particularly those of ethmoid and sphenoid, and less commonly, the frontal and orbital complications from these sinus infections can cause thrombophlebitis of the cavernous sinus. The valveless nature of the veins connecting the cavernous sinus causes easy spread of infection. The onset of cavernous sinus thrombophlebitis is abrupt, with chills and rigors. Eyelids get swollen with chemosis and proptosis of the eyeball. Cranial nerves III, IV, and VI related to the sinus are involved individually and sequentially, causing total ophthalmoplegia. The pupil becomes dilated and fixed, and the optic disc shows congestion and edema with a diminution of vision. Sensation in the distribution of V1 (ophthalmic division of CN V) is diminished. CSF is usually normal. The condition needs to be differentiated from orbital cellulitis Treatment Antibiotics and attention to the focus of infection, drainage of infected ethmoid or sphenoid sinus.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. Engorgement of retinal veins upon ophthalmoscopic examination is a true statement. Option C. Ptosis and ophthalmoplegia occur in cavernous sinus thrombosis due to the involvement of III, IV, and V cranial nerves. Option D. Ptosis and ophthalmoplegia occur in cavernous sinus thrombosis due to the involvement of III, IV, and V cranial nerves. In meningitis, significant neck rigidity will be present.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 40-year-old patient presented with nasal obstruction and dull pain over the upper cheeks, between eyes, and forehead. He also complained of decreased sense of smell, cough, and ear pain. He complained that the symptoms had been present for more than six months. According to the following condition, what can be the diagnosis?", "options": [{"label": "A", "text": "Chronic Rhinitis", "correct": true}, {"label": "B", "text": "Irritative Rhinitis", "correct": false}, {"label": "C", "text": "Viral Rhinitis", "correct": false}, {"label": "D", "text": "Rhinitis Sicca", "correct": false}], "correct_answer": "A. Chronic Rhinitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Chronic Rhinitis As symptoms have been present for over six months, other history and examination findings indicate that the answer will be chronic rhinitis.</p>\n<p><strong>Highyeild:</strong></p><p>CHRONIC RHINITIS Chronic rhinitis usually occurs due to recurrent attacks of acute rhinitis due to predisposing factors which lead to chronicity . The clinical features are nasal obstruction and discharge with swollen turbinate, which pit on touch. Mucoid or mucopurulent post-nasal discharge is also seen.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption B. Irritative rhinitis is a form of acute rhinitis caused by exposure to dust smoke or irritating gases such as ammonia and formalin. O ption C. Viral rhinitis has a burning sensation at the back of the nose, nasal stuffiness, rhinorrhea, and sneezing. Option D. The crust-forming disease, also known as rhinitis sicca, is caused due to exposure to hot, dry, dusty surroundings, especially in bakers Gold Smith and Ironsmith.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following are predisposing factors for developing simple chronic rhinitis?", "options": [{"label": "A", "text": "Persistence of nasal infection due to sinusitis or tonsillitis", "correct": false}, {"label": "B", "text": "Chronic irritation from dust, smoke, etc", "correct": false}, {"label": "C", "text": "Presence of nasal obstruction due to deviated nasal septum or synechiae leading to persistent discharge in the nose", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above All the above options are predisposing factors for the development of simple chronic rhinitis . So the answer is option D.</p>\n<p><strong>Highyeild:</strong></p><p>SIMPLE CHRONIC RHINITIS Simple chronic rhinitis is an early stage of hypertrophic rhinitis. There is hyperemia and mucous membrane edema with seromucous glands' hypertrophy and increased goblet cells. Clinical Features Nasal obstruction - Usually worse on lying and affects the dependent side of the nose. Nasal discharge - It may be mucoid or mucopurulent Headache - It is due to swollen turbinates impinging on the nasal septum. Swollen turbinates - Nasal mucosa is dull red. Turbinates are swollen; they pit on pressure and shrink with vasoconstrictor drops (this differentiates the condition from hypertrophic rhinitis). Treatment Treat the cause with particular attention to sinuses Predisposing factors for the development of simple chronic rhinitis . Recurrent attack of acute rhinitis in the presence of predisposing factors leads to chronicity. The predisposing factors include Persistence of nasal infection Chronic irritation from dust or smoke Vasomotor rhinitis Nasal obstruction Endocrine or metabolic factor</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Persistence of nasal infection due to sinusitis or tonsillitis is a predisposing factor for developing simple chronic rhinitis. O ption B. Chronic irritation from dust, smoke, etc., is a predisposing factor for the development of chronic simple rhinitis. Option C. Presence of nasal obstruction due to deviated nasal septum or synechiae leading to persistent discharge in the nose is also a predisposing factor for developing simple chronic rhinitis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 17 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 12</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Congenital Diseases of Larynx - Laryngomalacia - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 12</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 12 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Which is not true about laryngomalacia?", "options": [{"label": "A", "text": "Omega-Shaped Epiglottis", "correct": false}, {"label": "B", "text": "Stridor increases on crying but decreases on placing the child in the prone position", "correct": false}, {"label": "C", "text": "Most common congenital anomaly of the larynx", "correct": false}, {"label": "D", "text": "Surgical management of the airway by tracheostomy is the preferred initial treatment", "correct": true}], "correct_answer": "D. Surgical management of the airway by tracheostomy is the preferred initial treatment", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Surgical management of the airway by tracheostomy is the preferred initial treatment Treatment of laryngomalacia is mostly conservative . A tracheostomy may be done if severe respiratory obstruction. Tracheostomy is not the preferred initial treatment.</p>\n<p><strong>Highyeild:</strong></p><p>Laryngomalacia is the M/C congenital anomaly of the larynx It is the M/C condition causing inspiratory stridor afterbirth The stridor worsens during sleep and when the baby is in the supine position . Relieved in the prone position. On laryngoscopy – Epiglottis is omega-shaped . Treatment - conservative management .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Epiglottis is omega-shaped in laryngomalacia. Option: B. Stridor in laryngomalacia increases on crying but decreases on placing the child in the prone position. Option: C. Most common congenital anomaly of the larynx is laryngomalacia.</p>\n<p><strong>Extraedge:</strong></p><p>Laryngomalacia cannot be diagnosed in a paralyzed patient. Mostly, treatment is conservative. A tracheostomy may be required for some cases of severe respiratory obstruction. Supraglottoplasty is required in cases of severe laryngomalacia.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 1 year old child presents with the complaint of inspiratory stridor which increases on crying but decreases on placing the child in the prone position. Diagnosis of laryngomalacia was made. Which of the following is the most common mode of treatment for laryngomalacia?", "options": [{"label": "A", "text": "Reassurance", "correct": true}, {"label": "B", "text": "Tracheostomy", "correct": false}, {"label": "C", "text": "Surgery", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "A. Reassurance", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Reassurance. In most patient s, laryngomalacia is a self-limiting condition . Treatment of laryngomalacia is reassurance to the parents .</p>\n<p><strong>Highyeild:</strong></p><p>Laryngomalacia is the M/C congenital anomaly of the larynx It is the M/C condition causing inspiratory stridor afterbirth The stridor worsens during sleep and when the baby is in the supine position . Relieved in the prone position. On laryngoscopy – Epiglottis is omega-shaped . Treatment - conservative management .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Tracheostomy is required only in severe respiratory obstruction. Option: C. Surgical intervention (supraglottoplasty i.e. reduction of redundant laryngeal mucosa) is indicated for 10% of patients. The main indications for surgery are: Severe stridor Apnea Failure to thrive</p>\n<p><strong>Extraedge:</strong></p><p>Laryngomalacia cannot be diagnosed in a paralyzed patient. Mostly, treatment is conservative. A tracheostomy may be required for some cases of severe respiratory obstruction. Supraglottoplasty is required in cases of severe laryngomalacia.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The most common cause of inspiratory stridor in a 10-day-old child shortly afterbirth is", "options": [{"label": "A", "text": "Laryngomalacia", "correct": true}, {"label": "B", "text": "Foreign Body in Bronchi", "correct": false}, {"label": "C", "text": "Tracheal stenosis", "correct": false}, {"label": "D", "text": "laryngeal papilloma", "correct": false}], "correct_answer": "A. Laryngomalacia", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Laryngomalacia Laryngomalacia is the most common cause of inspiratory stridor in neonates. The stridor in case of laryngomalacia is not constantly present, rather it is intermittent. So laryngomalacia is also the M/C cause of intermittent stridor in neonates.</p>\n<p><strong>Highyeild:</strong></p><p>'</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. In the bronchial foreign body, there will be expiratory stridor. Option: C. In tracheal stenosis, there will be expiratory stridor. Option: D. In laryngeal papilloma there will be biphasic stridor.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 46-year-old patient came to the ENT department present with a hoarse voice. The patient gave the history that he has recovered from a chest infection and was admitted to ICU which required intubation for 3 weeks. Based on the given history, what can be the diagnosis? An image of such benign growth is shown below. Choose the correct option.", "options": [{"label": "A", "text": "Vocal nodule", "correct": false}, {"label": "B", "text": "Intubation Granuloma", "correct": true}, {"label": "C", "text": "Laryngeal Web", "correct": false}, {"label": "D", "text": "Laryngomalacia", "correct": false}], "correct_answer": "B. Intubation Granuloma", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994624986-QTDE008004IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Intubation Granuloma As there is a history of intubation for a long time of 3 weeks, so due to repeated friction by endotracheal tube, granuloma happens called intubation granuloma. Usually, they involve the posterior 1/3rd of vocal cords.</p>\n<p><strong>Highyeild:</strong></p><p>INTUBATION GRANULOMA results from injury to vocal processes of arytenoids due to rough intubation or prolonged presence of a tube between the cords. Mucosal ulceration is followed by granuloma formation over the exposed cartilage. They are bilateral involving posterior thirds of true cords.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. vocal nodule appears symmetrically on the free edge of vocal cords. Vocal nodules Option: C. laryngeal web is due to incomplete recanalization of the larynx Option: D. In laryngomalacia omega-shaped epiglottis will be seen.</p>\n<p><strong>Extraedge:</strong></p><p>They present with hoarseness and if large, dyspnoea as well. Treatment is voice rest and endoscopic removal of the granuloma.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A triumphant blower presented with h/o swelling in the neck which becomes prominent on doing the Valsalva manoeuvre. What is the diagnosis?", "options": [{"label": "A", "text": "Brachial Cyst", "correct": false}, {"label": "B", "text": "External Laryngeocoele", "correct": true}, {"label": "C", "text": "Metastatic Lymph Node", "correct": false}, {"label": "D", "text": "Ranula", "correct": false}], "correct_answer": "B. External Laryngeocoele", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994625017-QTDE008005IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>External Laryngeocoele As in the above question it is given that the patient is a trumpet player and swelling becomes prominent on the Valsalva maneuver . So it is an external laryngocele .</p>\n<p><strong>Highyeild:</strong></p><p>Laryngocele is an air-filled cystic swelling due to dilatation of the saccule External in which distended saccule herniates through the thyroid membrane and presents in the neck It arises from raised trans glottic air pressure as in trumpet players, glass-blowers, or weight lifters. It presents as a reducible swelling in the neck which increases in size on coughing or performing Valsalva Laryngocele left side as seen on Valsalva (arrow). Laryngocele mixed type with internal and external components.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Branchial cyst presents as cystic swelling in front of the sternocleidomastoid muscle. It is present at birth on one side of the neck Option: C. lymph nodes don’t increase or decrease on Valsalva maneuvre. Option: D. In ranula there is no outer swelling.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment is surgical excision through an external neck incision. Marsupialization of an internal laryngocele can be done by laryngoscopy but there are chances of recurrence. A laryngocele in an adult may be associated with carcinoma which causes obstruction of the saccule.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient presents to the emergency with epistaxis. Which of the following blood vessels does not take part in anastomosis in the area encircled in red of the nasal septum? Choose the correct option.", "options": [{"label": "A", "text": "Sphenopalatine Artery", "correct": false}, {"label": "B", "text": "Superior Labial Artery", "correct": false}, {"label": "C", "text": "Posterior Ethmoidal Artery", "correct": true}, {"label": "D", "text": "Greater Palatine Artery", "correct": false}], "correct_answer": "C. Posterior Ethmoidal Artery", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994625066-QTDE008007IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Posterior Ethmoidal Artery The area marked by the arrow is called Little’s area/Kisselbach’s plexus. The posterior ethmoidal artery is not involved in this plexus/ area.</p>\n<p><strong>Highyeild:</strong></p><p>LITTLE’S AREA is situated in the anterior inferior part of the nasal septum. Four arteries —an anterior ethmoidal, septal branch of superior labial, septal branch of sphenopalatine, and the greater palatine, anastomose here to form a vascular plexus called “ Kiesselbach’s plexus. ”</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A . Sphenopalatine artery is involved in Kisselbach plexus formation. Option: B. Superior labial artery is also involved in Kisselbach plexus formation. Option: D. Greater palatine artery is also involved in Kisselbach plexus formation.</p>\n<p><strong>Extraedge:</strong></p><p>WOODRUFF’S PLEXUS It is a plexus of veins situated inferior to the posterior end of the inferior turbinate. It is a site of posterior epistaxis in adults.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following congenital lesions do not produce stridor?", "options": [{"label": "A", "text": "Laryngomalacia", "correct": false}, {"label": "B", "text": "Laryngeal papilloma", "correct": false}, {"label": "C", "text": "Subglottic Stenosis", "correct": false}, {"label": "D", "text": "Laryngocele", "correct": true}], "correct_answer": "D. Laryngocele", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Laryngocele Laryngocele is the dilatation of the laryngeal saccule and extends between the thyroid cartilage and saccule. It does not produce stridor.</p>\n<p><strong>Highyeild:</strong></p><p>Stridor is noisy respiration produced by turbulent airflow through narrowed air passages. It is seen in the laryngeal web, cyst, and subglottic stenosis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In laryngomalacia, inspiratory stridor will be seen. Option: B. In laryngeal papilloma, biphasic stridor will be seen. Option: C. In subglottic stenosis also biphasic stridor will be seen.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment is surgical excision through an external neck incision. Marsupialization of an internal laryngocele can be done by laryngoscopy but there are chances of recurrence. A laryngocele in an adult may be associated with carcinoma which causes obstruction of the saccule.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 3 year old child presented in the ENT OPD with fever, barking cough, and stridor. The X-ray shows the given image. What can be the diagnosis based on the history and x-ray given below?", "options": [{"label": "A", "text": "Croup", "correct": true}, {"label": "B", "text": "Laryngomalacia", "correct": false}, {"label": "C", "text": "Smoking", "correct": false}, {"label": "D", "text": "Quinsy", "correct": false}], "correct_answer": "A. Croup", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994625068-QTDE008009IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Croup X-Ray shows a steeple sign . It is due to the tapered narrowing of the subglottis in the case of laryngo tracheal bronchitis/ croup .</p>\n<p><strong>Highyeild:</strong></p><p>Acute laryngotracheobronchitis/croup is caused by the parainfluenza virus in children of age group 6 months to 3 years.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B . In laryngomalacia, omega-shaped epiglottis will be seen. Option: C . In smoking barking cough and steeple sign on the x-ray is not seen. Option: D . In quinsy/ peritonsillar abscess hot potato voice will be seen.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The narrowest part of the infantile larynx is", "options": [{"label": "A", "text": "Supraglottic", "correct": false}, {"label": "B", "text": "Subglottic", "correct": true}, {"label": "C", "text": "Glottic", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "B. Subglottic", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Subglottic Subglottis is the narrowest part of the infantile larynx. Glottis is the narrowest part of the adult larynx.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A neonate while suckling milk can respire without difficulty due to:", "options": [{"label": "A", "text": "Start Soft Palate", "correct": false}, {"label": "B", "text": "Small Tongue", "correct": false}, {"label": "C", "text": "High Larynx", "correct": true}, {"label": "D", "text": "Small Pharynx", "correct": false}], "correct_answer": "C. High Larynx", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>High Larynx The infant's larynx is positioned high at the neck level of the glottis opposite to or C4 at rest and reaches C1 or C2 during swallowing . This high position allows the epiglottis to meet the soft palate and make the nasopharyngeal channel for nasal breathing during suckling . The milk feed passes separately over the dorsum of the tongue and the side of the epiglottis, thus allowing breathing and feeding to go on simultaneously.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Laryngofissure is", "options": [{"label": "A", "text": "Opening the larynx in the midline", "correct": true}, {"label": "B", "text": "Removal of thyroid", "correct": false}, {"label": "C", "text": "Removal of arytenoids", "correct": false}, {"label": "D", "text": "Removal of epiglottis", "correct": false}], "correct_answer": "A. Opening the larynx in the midline", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Opening the larynx in the midline Laryngofissure is a midline surgical opening of the larynx by an incision through the thyroid cartilage especially for the removal of a tumor.</p>\n<p><strong>Highyeild:</strong></p><p>In Glottic T1-carcinoma Radiotherapy is the treatment of choice. If radiotherapy is refused or not available, excision of the cord by endoscopic CO2 laser or laryngofissure is performed.</p>\n<p><strong>Random:</strong></p><p>In Glottic T1-carcinoma Radiotherapy is the treatment of choice. If radiotherapy is refused or not available, excision of the cord by endoscopic CO2 laser or laryngofissure is performed.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 2-month old child who was born by normal delivery to a primigravida lady presented with hoarseness of voice. Below are the findings on laryngoscopy. Diagnosis:", "options": [{"label": "A", "text": "Laryngomalacia", "correct": false}, {"label": "B", "text": "Juvenile papillomatosis", "correct": true}, {"label": "C", "text": "Reinke Edema", "correct": false}, {"label": "D", "text": "Malignancy", "correct": false}], "correct_answer": "B. Juvenile papillomatosis", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994625093-QTDE008013IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Juvenile papillomatosis Juvenile papillomatosis is seen in a baby born to a mother who was infected with the human papilloma virus.</p>\n<p><strong>Highyeild:</strong></p><p>Juvenile papillomatosis is the most common benign neoplasm of the larynx in children. It is viral in origin and is caused by human papilloma DNA virus type 6 and 11. It is presumed that affected children got the disease at birth from their mothers who had vaginal human papillomavirus disease.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A . In laryngomalacia, omega-shaped epiglottis will be seen. Option: C . Edema of reinke space causes reinke edema. Option: D . Malignancy is a rare feature as no B symptoms feature given in question.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 22 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 13</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Csom - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 13</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 13 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "False about the complication Of CSOM:", "options": [{"label": "A", "text": "Bezold abscess - Pus bursting through the medial side of the tip of the mastoid and collecting under the sternomastoid or digastric triangle.", "correct": false}, {"label": "B", "text": "The major Meningeal pathogens are H.influenza and S.pneumonia", "correct": false}, {"label": "C", "text": "Gardingo syndrome: lateral rectus palsy, orbital pain and middle ear infection", "correct": false}, {"label": "D", "text": "Commonest site of brain abscess is the occipital lobe", "correct": true}], "correct_answer": "D. Commonest site of brain abscess is the occipital lobe", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Commonest site of brain abscess is the occipital lobe Temporal lobe abscess is the commonest site of brain abscess . It occurs as an intracranial complication of CSOM.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Bezold abscess is one of the complications of CSOM in which Pus bursts through the medial side of the tip of the mastoid and collects under the sternomastoid or digastric triangle. OPTION B- The major Meningeal pathogens are influenza and S. pneumonia . This statement is true. OPTION C- Gardingo syndrome is seen in petrositis in which there is lateral rectus palsy, orbital pain and middle ear infection.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 2-year-old child presents with profuse odourless ear discharge following a URTI. An otoscopic examination shows the image below. Diagnosis is", "options": [{"label": "A", "text": "Tubotympanic type of CSOM", "correct": true}, {"label": "B", "text": "Atticoantral type of CSOM", "correct": false}, {"label": "C", "text": "Serous Otitis Media", "correct": false}, {"label": "D", "text": "Ear Polyp", "correct": false}], "correct_answer": "A. Tubotympanic type of CSOM", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685002714721-QTDE034003IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tubotympanic type of CSOM The above image shows central tympanic membrane perforation. Odourless ear discharge in a child after a chronic infection along with central perforation of the tympanic membrane indicates a tubotympanic type of CSOM.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION B- In the atticoantral type of CSOM there is foul-smelling discharge along with marginal perforation of the tympanic membrane. OPTION C- In serous otitis media there is no discharge from the ear. OPTION D- in the ear polyp there is a mass seen.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 7yrs old child came to OPD with scanty, foul-smelling ear discharge. On examination, perforation was seen in the pars flaccida of the tympanic membrane. Management includes", "options": [{"label": "A", "text": "Topical antibiotics with decongestant", "correct": false}, {"label": "B", "text": "Tympanoplasty", "correct": false}, {"label": "C", "text": "IV antibiotics and follow-up", "correct": false}, {"label": "D", "text": "Tympanomastoid Exploration", "correct": true}], "correct_answer": "D. Tympanomastoid Exploration", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tympanomastoid Exploration The given case scenario is an Atticoantral type of CSOM where the mainstay treatment is surgery - tympanomastoid exploration.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Topical antibiotics with decongestant preferred only in case of Tubotympanic CSOM where there are no bony erosions & complications are rare. OPTION B- Tympanoplasty is also preferred only in the case of Tubotympanic CSOM where there are no bony erosions & complications are rare. OPTION C- IV antibiotics and follow-up is also preferred only in the case of Tubotympanic CSOM where there are no bony erosions & complications are rare.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "True about CSOM:", "options": [{"label": "A", "text": "Etiology is multiple bacteria", "correct": true}, {"label": "B", "text": "Oral antibiotics are not affected", "correct": false}, {"label": "C", "text": "Ear drops are best", "correct": false}, {"label": "D", "text": "Otitic hydrocephalus is a known complication", "correct": false}], "correct_answer": "A. Etiology is multiple bacteria", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Etiology is multiple bacteria CSOM is caused by multiple bacteria - both aerobic and anaerobic.- There is no sex predilection in CSOM - both sexes are affected equally. Treatment of Tubotympanic type of CSOM is aural toileting and antibiotic ear drops. As far as oral antibiotics are concerned. “They are useful in acute exacerbation of chronically infected ear, otherwise role of systemic antibiotics in the treatment of CSOM is limited.”</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Griesinger's sign is seen in", "options": [{"label": "A", "text": "Lateral Sinus Thrombophlebitis", "correct": true}, {"label": "B", "text": "Meningitis", "correct": false}, {"label": "C", "text": "Brain Abscess", "correct": false}, {"label": "D", "text": "Cerebellar Abscess", "correct": false}], "correct_answer": "A. Lateral Sinus Thrombophlebitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lateral Sinus Thrombophlebitis Griesinger’s sign is due to thrombosis of the mastoid emissary vein . There is oedema over the posterior part of the mastoid.</p>\n<p><strong>Highyeild:</strong></p><p>Lateral Sinus Thrombophlebitis Stages Formation of Peri sinus abscess ↓ Stage of Mural Thrombus formation : due to ↓ collection of fibrin, nspired platelets and blood cells Stage of obliteration of sinus ⇒ Most of the ↓ signs is d/t this stage 4. Extension of Thrombus</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is characteristic of T.B otitis media:", "options": [{"label": "A", "text": "Marginal Perforation", "correct": false}, {"label": "B", "text": "Attic Perforation", "correct": false}, {"label": "C", "text": "Large Central Perforation", "correct": false}, {"label": "D", "text": "Multiple Perforation", "correct": true}], "correct_answer": "D. Multiple Perforation", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Multiple Perforation Multiple perforations in the tympanic membrane are seen in tubercular otitis media. (This feature was once considered characteristic of TB but now is seldom seen).</p>\n<p><strong>Highyeild:</strong></p><p>Tubercular Otitis Media Patients present with chronic painless otorrhoea (usually foul-smelling) which is resistant to antibiotic treatment. Severe conductive type hearing loss . (sometimes due to involvement of labyrinth may be SNHL) Facial nerve palsy may be the presenting symptom in children On examination Multiple perforation in the tympanic membrane . The middle ear and mastoid are filled with pale granulation tissue (characteristic of tuberculous otitis media).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 25 yrs. old patient presents with scanty, blood-tinged and foul-smelling discharge from the right ear. Next step of management:", "options": [{"label": "A", "text": "Schuller’S View", "correct": true}, {"label": "B", "text": "Law’s View", "correct": false}, {"label": "C", "text": "Towne’S View", "correct": false}, {"label": "D", "text": "Water’s View", "correct": false}], "correct_answer": "A. Schuller’S View", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Schuller’S View The patient presents with symptoms of the unsafe type of CSOM (Atticoantral type or squamosal type). It involves middle ear cleft ( attic, antrum, posterior tympanum and mastoid) and is associated with cholesteatoma. Cholesteatoma causes destruction of the attic and antrum ( key area). Schuller’s view is best to see the key area and the extent of the destruction. This cephalocaudal beam makes an angle of 30° to the sagittal .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION B- In Law’s view key area of the mastoid is not well seen. It is a lateral oblique view of the mastoid. OPTION C- Towne’s view is usually taken for acoustic neuroma and apical petrositis. OPTION D- Water’s view is for the nose and paranasal sinuses.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 32 yrs. Old male present with copious, non-foul smelling, mucopurulent discharge from the left ear. There is a decrease in hearing. Next step of management-", "options": [{"label": "A", "text": "Aural Toilet", "correct": true}, {"label": "B", "text": "Steroid Therapy", "correct": false}, {"label": "C", "text": "Surgery", "correct": false}, {"label": "D", "text": "Antibiotics Ear Drops", "correct": false}], "correct_answer": "A. Aural Toilet", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Aural Toilet The patient presents with the symptoms of the active mucosal type of CSOM (tubotympanic). An aural toilet is removal of discharge and debris from the ear. It can be done by dry mopping with absorbent cotton buds, suction clearance under a microscope or irrigation with sterile normal saline.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - OPTION B- Use of antibiotics and steroids is done after ear toileting. OPTION C- Surgery is done once the ear is dry. Myringoplasty with or without ossicular reconstruction can be done to restore hearing. OPTION D- Use of antibiotics and steroids is done after ear toileting</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An ENT surgeon sees a child with permanent perforation of the tympanic membrane. Which of the following is a feature of such perforation?", "options": [{"label": "A", "text": "It is seen in acute suppurative otitis media", "correct": false}, {"label": "B", "text": "It is caused by necrotizing otitis externa", "correct": false}, {"label": "C", "text": "It has squamous epithelium at its edges", "correct": true}, {"label": "D", "text": "Medical management can be tried initially", "correct": false}], "correct_answer": "C. It has squamous epithelium at its edges", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>It has squamous epithelium at its edges A permanent perforation is lined by squamous epithelium at its edges , hindering spontaneous healing.</p>\n<p><strong>Highyeild:</strong></p><p>Features of a permanent perforation: Seen in chronic suppurative otitis media (CSOM) Is similar to an epithelial-lined fistulous tract Cannot be medically managed, thus mandating surgery</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Perforation is seen in chronic suppurative otitis media (CSOM), not ASOM. OPTION B- Perforation is not seen in necrotizing otitis externa. OPTION D- Perforation Cannot be medically managed, thus mandating surgery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A First year postgraduate is evaluating 23-year-old patients with chronic suppurative otitis media in the ward. Which of the following is true regarding the tubotympanic type of disease?", "options": [{"label": "A", "text": "Tympanoplasty is the surgical treatment", "correct": true}, {"label": "B", "text": "Bloody ear discharge is seen", "correct": false}, {"label": "C", "text": "The discharge is scanty and foul smelling", "correct": false}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "A. Tympanoplasty is the surgical treatment", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tympanoplasty is the surgical treatment Tympanoplasty is the surgical treatment in tubotympanic type of CSOM whereas canal wall procedures are done in atticoantral type.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION B- Bloody ear discharge is seen in the atticoantral type of CSOM. OPTION C- The discharge is scanty and foul smelling is seen in the atticoantral type of CSOM.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 23-year-old Patient presents with high fever, neck rigidity, signs of raised ICT and a past history of chronic otitis media likely diagnosis is:", "options": [{"label": "A", "text": "Brain Abscess", "correct": false}, {"label": "B", "text": "Pyogenic Meningitis", "correct": true}, {"label": "C", "text": "Acute Subarachnoid Haemorrhage", "correct": false}, {"label": "D", "text": "Lateral Sinus Thrombosis", "correct": false}], "correct_answer": "B. Pyogenic Meningitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Pyogenic Meningitis High fever, neck rigidity, signs of raised intracranial pressure 23-year-oldive of pyogenic meningitis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Focal neurological deficits are not mentioned so brain abscess is not the right choice. Also, meningismus causing neck rigidity is generally not present in brain abscesses. OPTION C- Acute subarachnoid haemorrhage is due to rupture of intracranial aneurysm or head trauma. It presents with a sudden loss of consciousness. It may also present with sudden onset very severe headache described as worst ever headache. OPTION D- Lateral sinus thrombosis presents with fever, tenderness over the jugular vein and oedema over the mastoid.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Ideal site for myringotomy and grommet insertion:", "options": [{"label": "A", "text": "Antero Superior Quadrant", "correct": false}, {"label": "B", "text": "Antero Inferior Quadrant", "correct": true}, {"label": "C", "text": "Postero Superior Quadrant", "correct": false}, {"label": "D", "text": "Postero Inferior Quadrant", "correct": false}], "correct_answer": "B. Antero Inferior Quadrant", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Antero Inferior Quadrant For serious otitis media/Grommet insertion, the ideal site is the anteroinferior quadrant .</p>\n<p><strong>Highyeild:</strong></p><p>Grommet Insertion Done in Glue ear Preferred site: Antero-Inferior quadrant</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 37-year-old diabetic male who has recently been discharged from the COVID care hospital 2 weeks back, now comes to your clinic with high-grade fever and epistaxis for 4 days. On examination of the nose, you find out a Black necrotic mass filling the nasal cavity and eroding the nasal septum and hard palate. On reviewing the history, you come to know that the patient had been given high-dose steroids as a part of Covid management. What will be your diagnosis and the treatment plan for this patient respectively?", "options": [{"label": "A", "text": "Aspergillosis, oral fluconazole for 14 days", "correct": false}, {"label": "B", "text": "Necrotizing fasciitis, I/V penicillin and gentamycin", "correct": false}, {"label": "C", "text": "Mucormycosis, Amphotericin B and surgical debridement of affected tissue", "correct": true}, {"label": "D", "text": "Aspergillosis, I/V fluconazole", "correct": false}], "correct_answer": "C. Mucormycosis, Amphotericin B and surgical debridement of affected tissue", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mucormycosis, Amphotericin B and surgical debridement of affected tissue It is a case of a fungal infection called Mucormycosis , which is commonly seen in uncontrolled diabetes or those taking immunosuppressive drugs. The patient is a diabetic and received high-dose steroids for Covid treatment, which predisposed him to the infection. Treatment - Amphotericin B and surgical debridement of the affected tissue.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Aspergillosis presents as a fungal ball which grows mostly in the maxillary sinus and presents with pain over the cheeks and sinusitis complaints. Hence ruled out. OPTION B- Necrotizing fasciitis is a bacterial infection which affects the skin and facia but never erodes the septum and the bone. Hence ruled out. OPTION D- Aspergillosis presents as a fungal ball which grows mostly in the maxillary sinus and presents with pain over the cheeks and sinusitis complaints. Hence ruled out.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 23 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 11</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Deaf Child, Tinnitus & Hyperbaric Oxygen Therapy - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 11</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 11 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "An 85-year-old man had died of a heart attack caused by severe arteriosclerosis. He was a deaf-mute. Histo pathologic examination of the temporal bones showed degeneration of the stria vascularis, the organ of Corti, and the saccular sensory epithelium with a variable degree of cochlear and vestibular nerve atrophy. The osseous labyrinth, the membranous utricle, and the ampullae of the semicircular canals appeared normal. What would be the anomaly of the inner ear?", "options": [{"label": "A", "text": "Alexander Dysplasia", "correct": false}, {"label": "B", "text": "Mondini Dysplasia", "correct": false}, {"label": "C", "text": "Scheibe Dysplasia", "correct": true}, {"label": "D", "text": "Bing Seibenmann Dysplasia", "correct": false}], "correct_answer": "C. Scheibe Dysplasia", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Scheibe Dysplasia As the bony labyrinth is normal and dysplasia is seen in the cochlea so it is a case of Scheibe dysplasia which is also known as cochleosaccular dysplasia.</p>\n<p><strong>Highyeild:</strong></p><p>SCHEIBE DYSPLASIA It is the most common inner ear anomaly. The bony labyrinth is normal. The superior part of the membranous labyrinth (utricle and semicircular ducts) is also normal. Dysplasia is seen in the cochlea and saccule; hence also called cochleosaccular dysplasia.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Alexander dysplasia affects only the basal turn of the membranous cochlea. Thus only high frequencies are affected. Residual hearing is present in low frequencies and can be exploited by amplification with hearing aids. Option: B. In Mondini dysplasia Only the basal coil is present or the cochlea is 1.5 turns. There is an incomplete partition between the scalae due to the absence of osseous spiral lamina. Condition is unilateral or bilateral. Option: D. In Bing-seibenmann dysplasia there is the complete absence of a membranous labyrinth.</p>\n<p><strong>Extraedge:</strong></p><p>It is inherited as an autosomal recessive nonsyndromic trait.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 15-year-old male child presented with persistent microscopic hematuria, and a family history of hematuria, early onset progressive sensorineural hearing loss, kidney insufficiency (progressive glomerulonephritis ) and corneal dystrophy. Diagnosis is?", "options": [{"label": "A", "text": "Alport Syndrome.", "correct": true}, {"label": "B", "text": "Waardenburg Syndrome.", "correct": false}, {"label": "C", "text": "Pendred Syndrome.", "correct": false}, {"label": "D", "text": "Usher Syndrome.", "correct": false}], "correct_answer": "A. Alport Syndrome.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Alport Syndrome. The above history and examination findings suggest that the patient is suffering from Alport syndrome.</p>\n<p><strong>Highyeild:</strong></p><p>ALPORT SYNDROME It is an autosomal dominant variety. Triad of Alport syndrome includes Sensorineural hearing loss Ocular anomalies Hereditary progressive glomerulonephritis</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Waardenburg Syndrome is an Autosomal dominant U/L or B/L type of SNHL and is a congenital condition associated with vitiligo, white forelock and heterochromia iridis. Option: C. Pendred Syndrome is congenital type, autosomal recessive. It has SNHL associated with Goitre. Option: D. Usher Syndrome is Delayed in onset and autosomal recessive type, SNHL associated with night blindness, and retinitis pigmentosa.</p>\n<p><strong>Extraedge:</strong></p><p>Autosomal recessive and C-linked inheritance of Alport is also known.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A mother brought a 10-month-old baby to the hospital with complaints of the baby sleeps even in loud noise. Which assessment of hearing technique is well suited in this case?", "options": [{"label": "A", "text": "Visual reinforcement audiometry", "correct": true}, {"label": "B", "text": "Play Audiometry", "correct": false}, {"label": "C", "text": "Arousal Test", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "A. Visual reinforcement audiometry", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Visual reinforcement audiometry Visual reinforcement audiometry test is well suited between the developmental age of 6 months to 2 years</p>\n<p><strong>Highyeild:</strong></p><p>Visual reinforcement audiometry test It is a conditioning technique in which a child is trained to look for an auditory stimulus by turning his head. This behaviour is reinforced by a flashing light or an animated toy. This test helps to determine the hearing threshold using standard audiometric techniques. The auditory stimulus is delivered by headphones or better still by inserting earphones which are accepted better and are also lightweight. The test is well-suited for the developmental age of 6 months to 2 years.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Play audiometry test can be used in children with developmental ages of 2–4 or 5 years. Option: C. Arousal test is done between 6 weeks to 16 weeks.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 50-year-old male patient come to OPD with complaints of a ringing sound or noise in his ear and described the origin of the sound as being within himself and the doctor can’t hear the sound with a stethoscope. What would be the probable symptom of that patient?", "options": [{"label": "A", "text": "Objective Tinnitus", "correct": false}, {"label": "B", "text": "Subjective Tinnitus", "correct": true}, {"label": "C", "text": "Otitis Media", "correct": false}, {"label": "D", "text": "Vertigo", "correct": false}], "correct_answer": "B. Subjective Tinnitus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Subjective Tinnitus Tinnitus is a ringing sound or noise in the ear . The characteristic feature is that the origin of this sound is within the patient. Subjective, which can only be heard by the patient.</p>\n<p><strong>Highyeild:</strong></p><p>SUBJECTIVE TINNITUS Subjective tinnitus may have its origin in the external ear, middle ear, inner ear, VIIIth nerve or the central nervous system. Systemic disorders like anaemia, arteriosclerosis, hypertension and certain drugs may act through the inner ear or central auditory pathways. In the presence of conductive hearing loss, the patient may hear abnormal noises in the head during eating, speaking or even respiration.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In objective hearing loss sound can be heard by doc with a stethoscope Option: C. In otitis media there is Ear pain, the feeling of fullness in the ear, Fluid draining from the ear fever, and Hearing loss. Option: D. Vertigo is a sudden internal or external spinning sensation, often triggered by moving and preceded by vomiting.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 36-year-old male patient presented with clicking noises in the ear and muscle spasms at the back of the throat. What would be the probable diagnosis?", "options": [{"label": "A", "text": "Auditory Hallucination.", "correct": false}, {"label": "B", "text": "Depression", "correct": false}, {"label": "C", "text": "Anxiety", "correct": false}, {"label": "D", "text": "Palatal Myoclonus.", "correct": true}], "correct_answer": "D. Palatal Myoclonus.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Palatal Myoclonus. Palatal myoclonus produces a clicking sound due to clonic contraction of the muscles of the soft palate and can be easily diagnosed.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In auditory hallucination patient hears voices that don’t exist in reality. Option: B. Anxiety mainly involves overwhelming feelings of worry, nervousness, and fear. Option: C. The main symptom of depression is typically a lingering low, sad, or hopeless mood</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 36-year-old male patient comes to the emergency department with weakness, pain in muscles and joints, headache, dizziness, and confusion after scuba diving. Now the patient has severe breathlessness and chest pain, from the X-ray there was pneumothorax. Would you prefer hyperbaric oxygen therapy for this patient?", "options": [{"label": "A", "text": "Yes", "correct": false}, {"label": "B", "text": "No", "correct": true}, {"label": "C", "text": "May Be", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "B. No", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>No Tension pneumothorax is an absolute contraindication to hyperbaric oxygenation (HBO) . During the decompression, at the end of the hyperbaric session, the increase in gas volume related to decreasing the pressure in the chamber can induce tension pneumothorax.</p>\n<p><strong>Highyeild:</strong></p><p>EVIDENCE-BASED INDICATIONS OF HYPERBARIC OXYGEN THERAPY Healing in problem wounds, diabetic or venous Necrotizing soft tissue damage including malignant otitis externa Radiation tissue damage Carbon monoxide poisoning Crush injury and other acute traumatic ischemia Decompression sickness Air or gas embolism. Sensorineural hearing loss.</p>\n<p><strong>Extraedge:</strong></p><p>Research-based Indications Bell palsy Burns Anoxic encephalopathy Traumatic brain injury Stroke Spinal cord injury Cerebral palsy/Autism</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient comes to OPD with complaints of hearing loss for the past few days. On examination, there was tympanic membrane perforation and erosion of the malleus was present. Which type of tympanolasty will you recommend?", "options": [{"label": "A", "text": "Type 1", "correct": false}, {"label": "B", "text": "Type 2", "correct": true}, {"label": "C", "text": "Type 3", "correct": false}, {"label": "D", "text": "Type 4", "correct": false}], "correct_answer": "B. Type 2", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Type 2 Type ll tympanoplasty is done when the Defect is perforation of the tympanic membrane with the erosion of the malleus. The graft is placed on the incus or remnant of the malleus.</p>\n<p><strong>Highyeild:</strong></p><p>TYPES OF TYMPANOPLASTY Type I Defect is perforation of tympanic mem- brane which is repaired with a graft. It is also called myringoplasty. Type II Defect is perforation of tympanic mem- brane with erosion of malleus. Graft is placed on the incus or remnant of malleus. Type III Malleus and incus are absent. Graft is placed directly on the stapes head. It is also called myringostapediopexy or columella tympanoplasty. Type IV Only the footplate of stapes is present. It is exposed to the external ear, and graft is placed between the oval and round win- dows. A narrow middle ear (cavum mi- nor) is thus created to have an air pocket around the round window. A mucosa-lined space extends from the eustachian tube to the round window. Sound waves in this case act directly on the footplate while the round window has been shielded Type V Stapes footplate is fixed but round window is functioning. In such cases, another win- dow is created on horizontal semicircular canal and covered with a graft. Also called fenestration operation.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Type I tympanoplasty is done when the Defect is perforation of the tympanic membrane which is repaired with a graft. It is also called myringoplasty. Option: C. Type III Tympanoplasty Malleus and incus are absent. The graft is placed directly on the stapes head. It is also called myringostapediopexy or columella tympanoplasty. Option: D. Type IV Tympanoplasty Only the footplate of the stapes is present. It is exposed to the external ear, and the graft is placed between the oval and round windows. A narrow middle ear (cavum minor) is thus created to have an air pocket around the round window. A mucosa-lined space extends from the eustachian tube to the round window. Sound waves in this case act directly on the footplate while the round window has been shielded.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 7-year-old child presented with a hole in his eardrum that hasn’t healed within three months and had repeated ear infections. And the hole in the centre of his eardrum. He wants to swim without waterproofing his ear. Water entering the hole can lead to infections. So, you decided to do myringoplasty on this child. In which technique of myringoplasty is the graft placed lateral to the fibrous layer of the tympanic membrane after removing squamous epithelium from the lateral surface of tympanic membrane remnants?", "options": [{"label": "A", "text": "Underlay Technique", "correct": false}, {"label": "B", "text": "Type 3 tympanoplasty", "correct": false}, {"label": "C", "text": "Ossicular Reconstruction", "correct": false}, {"label": "D", "text": "Overlay Technique", "correct": true}], "correct_answer": "D. Overlay Technique", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Overlay Technique In the overlay technique, the graft is placed lateral to the fibrous layer of the tympanic membrane after carefully removing all squamous epithelium from the lateral surface of the tympanic membrane remnant.</p>\n<p><strong>Highyeild:</strong></p><p>Myringoplasty is the repair of the tympanic membrane. Graft materials of choice are temporalis fascia or the perichondrium taken from the patient. Sometimes, homografts such as dura, vein, fascia or cadaver tympanic membrane are also used. The repair can be done by two techniques - the underlay or the overlay. In the overlay technique, the graft is placed lateral to the fibrous layer of the tympanic membrane after carefully removing all squamous epithelium from the lateral surface of the tympanic membrane remnant.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In the underlay technique, margins of perforation are freshened and the graft is placed medial to perforation or tympanic annulus, if large, and is supported by gel foam in the middle ear. Option: B. In Type 3 Tympanoplasty Malleus and incus are absent. The graft is placed directly on the stapes head. It is also called myringostapediopexy or columella tympanoplasty. Option: C. Ossicular reconstruction is used for replacing damaged ear ossicles.</p>\n<p><strong>Extraedge:</strong></p><p>In the underlay technique, margins of perforation are freshened and the graft is placed medial to perforation or tympanic annulus, if large, and is supported by gel foam in the middle ear.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 50-year-old male patient presented with complaints of total hearing loss in one ear without any lesion or perforation of the tympanic membrane. On investigation, the audiogram shows the absence of a shadow curve. What would be the probable diagnosis?", "options": [{"label": "A", "text": "Otitis Media", "correct": false}, {"label": "B", "text": "Conductive Hearing Loss", "correct": false}, {"label": "C", "text": "Sensorineural hearing loss", "correct": false}, {"label": "D", "text": "Non-organic hearing loss.", "correct": true}], "correct_answer": "D. Non-organic hearing loss.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Non-organic hearing loss. The above history and examination findings suggest that it is a case of non-organic hearing loss.</p>\n<p><strong>Highyeild:</strong></p><p>NON-ORGANIC HEARING LOSS In this type of hearing loss, there is no organic lesion. It is either due to malingering or psychogenic. In the former, usually, there is a motive to claim some compensation for being exposed to industrial noises, head injury or ototoxic medication. Total hearing loss in both ears, total loss in only one ear or exaggerated loss in one or both ears. Absence of shadow curve. Normally, a shadow curve can be obtained while testing bone conduction, if the healthy ear is not masked. This is due to the transcranial transmission of sound to the healthy ear. The absence of this curve in a patient complaining of unilateral deafness is diagnostic of Non-organic hearing loss.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In otitis media there is a type B tympanogram with a flat curve and normal canal volume. Option: B. In Conductive hearing loss type B audiogram is seen. Option: C. SNHL is usually permanent and can be mild, moderate, severe, profound, or total. Various other types can be used depending on the shape of the audiogram, such as high frequency, low frequency, and U-shaped depending on the shape of the audiogram.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A child born with congenital deafness and midline neck swelling presented to OPD. On radiological assessment, his aqueduct was found to be enlarged. A diagnosis of Pendred’s syndrome was made. Which of the following is true about it except?", "options": [{"label": "A", "text": "In Pendred syndrome SNHL type of hearing loss is seen", "correct": false}, {"label": "B", "text": "Autosomal dominant type of inheritance", "correct": true}, {"label": "C", "text": "Mondini type of malformation is seen in the cochlea", "correct": false}, {"label": "D", "text": "There is a defect in the organification of the iodine.", "correct": false}], "correct_answer": "B. Autosomal dominant type of inheritance", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Autosomal dominant type of inheritance Pendred’s syndrome is inherited in an autosomal recessive pattern of inheritance so that the chance of a subsequent child with the condition is 1 in 4.</p>\n<p><strong>Highyeild:</strong></p><p>PENDRED SYNDROME Pendred's syndrome is the most common syndromal form of deafness and is associated with developmental abnormalities of the cochlea, sensorineural hearing loss, and goitre. Pendred's syndrome is associated with temporal bone abnormalities ranging from a large vestibular aqueduct to the Mondini malformation.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In Pendred syndrome SNHL type of hearing loss is seen as a true statement. Option: C. Pendred's syndrome is associated with temporal bone abnormalities ranging from a large vestibular aqueduct to the Mondini malformation. Option: D. The condition is caused by a defect in the organification of iodine.</p>\n<p><strong>Extraedge:</strong></p><p>It is inherited in an autosomal recessive pattern of inheritance so that the chance of a subsequent child with the condition is 1 in 4. The condition is caused by a defect in the organification of iodine. The perchlorate test is used in the diagnosis of Pendred's syndrome.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A mother brought the 8-year-old male child to OPD with complaints of a child having poor performance in school, not being able to communicate with others and also having a history of sleeping in loud noise or failing to startle loud noise in his infant period. What will be the probable condition of that child?", "options": [{"label": "A", "text": "Deaf Child.", "correct": true}, {"label": "B", "text": "Tympanic Membrane Rupture.", "correct": false}, {"label": "C", "text": "Otosclerosis.", "correct": false}, {"label": "D", "text": "Tinnitus.", "correct": false}], "correct_answer": "A. Deaf Child.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Deaf Child. The above history and examination findings suggest that the child is deaf.</p>\n<p><strong>Highyeild:</strong></p><p>Hearing loss is suspected if The child sleeps through loud noises unperturbed or fails to startle by loud sounds. fails to develop speech at 1-2 years.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. In Tympanic membrane rupture patients present with Sudden hearing loss, and find it difficult to hear anything hearing may just be slightly muffled, earache or pain in the ear, Itching in the ear, fluid leaking from the ear and may have fever. Option: C. In otosclerosis there is hearing loss (slow at first, but worsens over time), Ringing in the ears (tinnitus), Vertigo or dizziness. Option: D. In Tinnitus there is a Ringing or buzzing noise in one or both ears that may be constant or come and go, often associated with hearing loss.</p>\n<p><strong>Extraedge:</strong></p><p>A partially hearing child may have defective speech and perform poorly in school and be labelled mentally retarded.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 21 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 30</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Disease of External Ear - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 30</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 30 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "All of the following are true about malignant otitis externa except?", "options": [{"label": "A", "text": "Occurs in patients with diabetes mellitus", "correct": false}, {"label": "B", "text": "Monitoring is done by gallium scan", "correct": false}, {"label": "C", "text": "Most common malignant tumor of the external ear", "correct": true}, {"label": "D", "text": "Surgical debridement is rarely needed", "correct": false}], "correct_answer": "C. Most common malignant tumor of the external ear", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Most common malignant tumor of the external ear The most common malignant tumor of the external ear is squamous cell carcinoma. Malignant otitis externa is a benign condition ; not malignant.</p>\n<p><strong>Highyeild:</strong></p><p>Malignant (necrotizing) otitis externa is an inflammatory condition caused by pseudomonas infection usually in the elderly, diabetics, or those on immunosuppressive drugs . It is not a tumor. Clinical feature- Severe otalgia in an elderly diabetic patient with granulation tissue in the external ear canal Gallium scan- for prognosis and response to treatment.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Malignant otitis externa is common in people with diabetes or immunocompromised. Option: B. G allium scan is done for prognosis and response to treatment. Option: D. Prolonged antibiotic treatment has replaced radical surgery and resections done earlier for this condition.</p>\n<p><strong>Extraedge:</strong></p><p>Antibiotics found effective in malignant otitis externa Gentamicin combined with ticarcillin. They are given intravenously. Gentamicin is both ototoxic and nephrotoxic, and ticarcillin may produce penicillin-like reactions.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All of the following are risk factors for malignant otitis Externa except?", "options": [{"label": "A", "text": "Diabetes", "correct": false}, {"label": "B", "text": "Immunodeficiency", "correct": false}, {"label": "C", "text": "Parotitis", "correct": true}, {"label": "D", "text": "Chemotherapy", "correct": false}], "correct_answer": "C. Parotitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Parotitis All conditions with immunodeficiency are the risk factors for malignant otitis externa. Examples- are diabetes, immunodeficiency, and chemotherapy. Parotitis is not a risk factor for malignant otitis externa.</p>\n<p><strong>Highyeild:</strong></p><p>Malignant (necrotizing) otitis externa is an inflammatory condition caused by pseudomonas infection usually in the elderly, diabetics, or those on immunosuppressive drugs . It is not a tumor. Clinical feature- Severe otalgia in an elderly diabetic patient with granulation tissue in the external ear canal Gallium scan- for prognosis and response to treatment.</p>\n<p><strong>Extraedge:</strong></p><p>Antibiotics found effective in malignant otitis externa Gentamicin combined with ticarcillin. They are given intravenously. Gentamicin is both ototoxic and nephrotoxic, and ticarcillin may produce penicillin-like reactions.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Diffuse otitis externa is also known as:", "options": [{"label": "A", "text": "Glue Ear", "correct": false}, {"label": "B", "text": "Malignant Otitis Externa", "correct": false}, {"label": "C", "text": "Telephonists Ear", "correct": true}, {"label": "D", "text": "ASOM", "correct": false}], "correct_answer": "C. Telephonists Ear", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Telephonists Ear Humidity and hot climate are one of the predisposing factors for otitis externa . Hence – otitis externa is also k/a Singapore ear (where the climate is hot & humid) Telephonist ear as talking on the phone causes humidity around the ear) or Swimmers ear.</p>\n<p><strong>Highyeild:</strong></p><p>Diffuse Otitis Externa</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Glue ear is serous otitis media. Option: B. malignant otitis externa is also called necrotizing otitis externa. Option: D. ASOM is acute suppurative otitis media. It is an infection of the middle ear.</p>\n<p><strong>Extraedge:</strong></p><p>Also, know Pseudomonas aeruginosa is a normal inhabitant of the external ear. Its numbers are kept in balance by the normal acidity of EAC. Prolonged swimming or abusive use of cotton typed earbuds can alter the pH, producing a more basic environment in which pseudomonas grows rapidly</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Malignant otitis externa is caused by:", "options": [{"label": "A", "text": "S. Aureus", "correct": false}, {"label": "B", "text": "S. Albus", "correct": false}, {"label": "C", "text": "P. Aeruginosa", "correct": true}, {"label": "D", "text": "E. Coli", "correct": false}], "correct_answer": "C. P. Aeruginosa", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>P. Aeruginosa Pseudomonas aeruginosa is the most common cause of malignant otitis externa.</p>\n<p><strong>Highyeild:</strong></p><p>Malignant Otitis Externa/Necrotising Otitis Externa Progressive debilitating and sometimes fatal infection of the external auditory canal, characterized by granulation tissue in the external auditory canal at the junction of bone and cartilage. Most common organism: Pseudomonas aeruginosa. Others: S. aureus S. epidermidis Aspergillus Actinomyces</p>\n<p><strong>Extraedge:</strong></p><p>Antibiotics found effective in malignant otitis externa Gentamicin combined with ticarcillin. They are given intravenously. Gentamicin is both ototoxic and nephrotoxic, and ticarcillin may produce penicillin-like reactions.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An elderly diabetic presents with painful ear discharge and edema of the external auditory canal with facial palsy, not responding to Antibiotics. Increased uptake on technetium scan is noted. The most probable Diagnosis is", "options": [{"label": "A", "text": "Malignant Otitis Externa", "correct": true}, {"label": "B", "text": "Malignancy of the middle ear", "correct": false}, {"label": "C", "text": "The infective disease of the middle ear", "correct": false}, {"label": "D", "text": "Malignancy of the nasopharynx with Eustachian tube obstruction", "correct": false}], "correct_answer": "A. Malignant Otitis Externa", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Malignant Otitis Externa As the patient is an elderly diabetic which is a risk factor for malignant otitis externa. Also painful ear discharge along with increased uptake on technetium scan suggests malignant otitis externa.</p>\n<p><strong>Highyeild:</strong></p><p>In refractory cases of otitis externa if it is not responding to antibiotics even after 7–10 days of treatment, always suspect malignant otitis externa. An elderly diabetic patient These features are highly suggestive of Malignant Otitis Externa + Painful ear discharge + Facial N palsy + No Response to treatment + ?'ed uptake on Technetium bone scan</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A new-born presents with bilateral microtia and external auditory canal atresia. Corrective surgery is usually performed at", "options": [{"label": "A", "text": "< 1 year of age", "correct": false}, {"label": "B", "text": "5-7 years of age", "correct": true}, {"label": "C", "text": "Puberty", "correct": false}, {"label": "D", "text": "Adulthood", "correct": false}], "correct_answer": "B. 5-7 years of age", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>5-7 years of age Treatment of Microtia - involves auricular reconstruction in multiple stages. Patients undergo observation until the age of 5 years to allow for the growth of rib cartilage which is harvested for reconstruction.</p>\n<p><strong>Highyeild:</strong></p><p>Microtia right ear (peanut ear).</p>\n<p><strong>Extraedge:</strong></p><p>It is frequently associated with anomalies of the external auditory canal, middle and internal ear. The condition may be unilateral or bilateral. Hearing loss is frequent. Peanut ear is a form of microtia.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Features of the moderately retracted tympanic membrane are all except", "options": [{"label": "A", "text": "Handle of malleus appearance foreshortened", "correct": false}, {"label": "B", "text": "Cone of light is absent or interrupted", "correct": false}, {"label": "C", "text": "Lateral process of malleus becomes more prominent", "correct": false}, {"label": "D", "text": "None of the above", "correct": true}], "correct_answer": "D. None of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>None of the above All of the above options are the features of a retracted tympanic membrane.</p>\n<p><strong>Highyeild:</strong></p><p>Characteristics of retracted tympanic membrane - It appears dull and lusterless. Cone of light is absent or interrupted. Handle of the malleus appears foreshortened. Lateral process of the malleus becomes more prominent. Anterior and posterior mallear folds become sickle-shaped. It is immobile or has limited mobility when tested with a pneumatic otoscope or single’s speculum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All of the following are true about malignant otitis externa except:", "options": [{"label": "A", "text": "ESR is used for follow-up after treatment", "correct": false}, {"label": "B", "text": "Granulation tissues are seen on the superior wall of the external auditory canal", "correct": false}, {"label": "C", "text": "Severe hearing loss is the chief presenting complaint", "correct": true}, {"label": "D", "text": "Pseudomonas is the most common cause", "correct": false}], "correct_answer": "C. Severe hearing loss is the chief presenting complaint", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Severe hearing loss is the chief presenting complaint Severe hearing loss is not the chief presenting complaint of malignant otitis externa. Instead Severe otalgia in an elderly diabetic patient with granulation tissue in the external ear canal .</p>\n<p><strong>Highyeild:</strong></p><p>Malignant (necrotizing) otitis externa is an inflammatory condition caused by pseudomonas infection usually in the elderly, diabetics, or in those on immunosuppressive drugs . It is not a tumor. Clinical feature- Severe otalgia in an elderly diabetic patient with granulation tissue in the external ear canal Gallium scan- for prognosis and response to treatment.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A . ESR is often raised and can be used to monitor the progress of disease Option: B. Granulation tissues are seen on superior wall of the external auditory canal Option: D. Pseudomonas is the most common cause</p>\n<p><strong>Extraedge:</strong></p><p>Antibiotics found effective in malignant otitis externa Gentamicin combined with ticarcillin. They are given intravenously. Gentamicin is both ototoxic and nephrotoxic, and ticarcillin may produce penicillin like reactions.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 52-year-old male who is a hard laborer works during morning 10 am – 4 pm in the evening. The patient presented with an ulcerated lesion with raised everted edges and indurated base. What is the most probable diagnosis?", "options": [{"label": "A", "text": "Squamous Cell Carcinoma", "correct": true}, {"label": "B", "text": "Neurofibroma", "correct": false}, {"label": "C", "text": "Basal Cell Carcinoma", "correct": false}, {"label": "D", "text": "Melanoma", "correct": false}], "correct_answer": "A. Squamous Cell Carcinoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Squamous Cell Carcinoma Here the patient presents with a painless nodule or an ulcer with raised everted edges and indurated base . Also, the disease is more commonly seen in males in their fifties who had prolonged exposure to sunlight.</p>\n<p><strong>Highyeild:</strong></p><p>Squamous cell carcinoma ear Squamous cell carcinoma of pinna.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: B. In neurofibroma, the patient presents as non-tender, firm swelling. Option: C. Basal cell carcinoma presents as a nodule with a central crust. Removal of the lesion results in bleeding. Option: D. Melanoma may occur anywhere over the auricle. It is more common in males of light complexion.</p>\n<p><strong>Extraedge:</strong></p><p>Metastases to regional lymph nodes occur very late. Disease is more common in males in their fifties who had prolonged exposure to direct sunlight. Fair-complexioned people are more prone.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 10-Year-old boy came to OPD with complaints of a sore throat for the past 5 days following severe pain in the right ear. On examination, the tympanic membrane has a cartwheel appearance which fully bulges and is impending to explode. What is the first step of treatment?", "options": [{"label": "A", "text": "Antibiotics And Analgesic", "correct": false}, {"label": "B", "text": "Radial incision of TM at Anteroinferior quadrant and drainage", "correct": false}, {"label": "C", "text": "Curvilinear incision of TM at posteroinferior quadrant and drainage", "correct": true}, {"label": "D", "text": "Incision of TM at posterosuperior quadrant and drainage", "correct": false}], "correct_answer": "C. Curvilinear incision of TM at posteroinferior quadrant and drainage", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Curvilinear incision of TM at posteroinferior quadrant and drainage This is a case of Acute otitis media where a curvilinear incision of TM at posteroinferior as it is safe and easily accessible.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Antibiotics and analgesics are used in the early stages of acute otitis media. Option: B. Radial Incision at Anteroinferior quadrant is made for serous otitis media. Option: D. Incision at the posterosuperior quadrant can damage important structures like chorda tympani, facial nerve, incudostapedial joint, and oval window.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 55-year-old diabetic male patient presented with a rapidly spreading infection of the EAC with involvement of the bone and the presence of granulation tissue. What is the diagnosis?", "options": [{"label": "A", "text": "Malignant Otitis Externa", "correct": true}, {"label": "B", "text": "Diffuse Otitis Externa", "correct": false}, {"label": "C", "text": "Furuncle", "correct": false}, {"label": "D", "text": "Otomycosis", "correct": false}], "correct_answer": "A. Malignant Otitis Externa", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Malignant Otitis Externa Old diabetic with rapidly spreading infection and granulations , the diagnosis is malignant Otitis externa . The most common cause of it is pseudomonas.</p>\n<p><strong>Highyeild:</strong></p><p>Malignant (necrotizing) otitis externa is an inflammatory condition caused by pseudomonas infection usually in the elderly, diabetics, or those on immunosuppressive drugs . It is not a tumor. Clinical feature- Severe otalgia in an elderly diabetic patient with granulation tissue in the external ear canal Gallium scan- for prognosis and response to treatment.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: B. DIFFUSE OTITIS EXTERNA is a diffuse inflammation of meatal skin that may spread to involve the pinna and epidermal layer of the tympanic membrane. Option: C . FURUNCLE (LOCALIZED ACUTE OTITIS EXTERNA) is a staphylococcal infection of the hair follicle. As the hair is confined only to the cartilaginous part of the meatus, furuncle is seen only in this part of the meatus. Option: D . OTOMYCOSIS is a fungal infection of the ear canal that often occurs due to Aspergillus niger, A.fumigatus, or Candida albicans. It is seen in hot and humid climates of tropical and subtropical countries.</p>\n<p><strong>Extraedge:</strong></p><p>Metastases to regional lymph nodes occur very late. The disease is more common in males in their fifties who had prolonged exposure to direct sunlight. Fair-complexioned people are more prone.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An elderly diabetic female presents with severe pain in the ear. On examination, he is found to have granulations in the external ear (see picture given below) conductive deafness, and facial palsy. He was found to be having an increased uptake of Tc99. The most probable diagnosis is", "options": [{"label": "A", "text": "Malignancy with middle ear infection", "correct": false}, {"label": "B", "text": "Nasopharyngeal carcinoma with ET blockade", "correct": false}, {"label": "C", "text": "Malignant Otitis Externa", "correct": true}, {"label": "D", "text": "CSOM", "correct": false}], "correct_answer": "C. Malignant Otitis Externa", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994655917-QTDE009013IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Malignant Otitis Externa This is a clear-cut case of malignant Otitis externa. For early diagnosis, a Tc scan is used. Tc is taken up by the osteoclasts and osteoblasts and indicates the bony erosion during the malignant Otitis externa.</p>\n<p><strong>Highyeild:</strong></p><p>Malignant (necrotizing) otitis externa is an inflammatory condition caused by pseudomonas infection usually in the elderly, diabetics, or those on immunosuppressive drugs . It is not a tumor. Clinical feature- Severe otalgia in an elderly diabetic patient with granulation tissue in the external ear canal Gallium scan- for prognosis and response to treatment.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Malignancy of the EAC usually follows long-standing ear discharge which has not been mentioned in the above question. A biopsy will be diagnostic of malignancy and a Tc scan is not required here. Option: B . Nasopharyngeal carcinoma with ET blockade presents with upper deep cervical lymphadenopathy with U/L serous otitis media. Option: D . In CSOM above clinical features don’t occur.</p>\n<p><strong>Extraedge:</strong></p><p>Metastases to regional lymph nodes occur very late. Disease is more common in males in their fifties who had prolonged exposure to direct sunlight. Fair-complexioned people are more prone.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 65-year-old diabetic presents with necrosis of the external auditory canal with foul-smelling discharge. The probable organism associated with the conditions is", "options": [{"label": "A", "text": "Hemophilus Influenza", "correct": false}, {"label": "B", "text": "Pseudomonas Aeruginosa", "correct": true}, {"label": "C", "text": "Streptococcus pyogenes", "correct": false}, {"label": "D", "text": "E. Coli", "correct": false}], "correct_answer": "B. Pseudomonas Aeruginosa", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Pseudomonas Aeruginosa Old diabetic with rapidly spreading infection and granulations, the diagnosis is malignant Otitis externa . The most common cause is pseudomonas</p>\n<p><strong>Highyeild:</strong></p><p>Malignant (necrotizing) otitis externa is an inflammatory condition caused by pseudomonas infection usually in the elderly, diabetics, or those on immunosuppressive drugs . It is not a tumor. Clinical feature- Severe otalgia in an elderly diabetic patient with granulation tissue in the external ear canal Gallium scan- for prognosis and response to treatment.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45-year-old patient comes to the clinic and presents with pain and discharge in the ear. On examination the ear shows the following. He should be managed by:", "options": [{"label": "A", "text": "Ciprofloxacin Ear Drops", "correct": false}, {"label": "B", "text": "Clotrimazole ear drops", "correct": true}, {"label": "C", "text": "Wax softening ear drops", "correct": false}, {"label": "D", "text": "Steroid ear drops", "correct": false}], "correct_answer": "B. Clotrimazole ear drops", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994656225-QTDE009015IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Clotrimazole ear drops Otomycosis is a fungal infection of the ear Canal that is often caused due to aspergillus Niger. It is seen in hot and humid climates. Treatment consists of a toilet to remove all discharge and epithelial debris which are conducive to the growth of fungus. It can be done by syringing, suction or mopping. Specific treatment can be applied as nystatin effective against candida. Clotrimazole ear drop is not given.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45-year-old patient with conductive hearing loss came to your ENT clinic. On examination of the external auditory Canal, wax is seen. You decided to do syringing to remove wax. What should be the direction of the water Jet while doing syringing of the ear:", "options": [{"label": "A", "text": "Anteroinferior", "correct": false}, {"label": "B", "text": "Anterosuperior", "correct": false}, {"label": "C", "text": "Posterosuperior", "correct": true}, {"label": "D", "text": "Posteroinferior", "correct": false}], "correct_answer": "C. Posterosuperior", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Posterosuperior Syringing should be done in the posterosuperior direction.</p>\n<p><strong>Highyeild:</strong></p><p>Technique of syringing the ear Pinna is pulled upwards and backward and a stream of water from the ear syringe is directed along the posterior superior wall of the meatus. Pressure of water, built up deeper into the wax, expels the wax out.</p>\n<p><strong>Extraedge:</strong></p><p>It is necessary before syringing to ask the patient for any past history of ear discharge or an existing perforation. A quiescent otitis media may be reactivated by syringing.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45-year-old patient with conductive hearing loss came to your ENT clinic. On examination of the external auditory Canal, wax is seen. You would like to do syringing to remove wax. A person develops a cough while performing syringing. Which nerve is responsible for cough?", "options": [{"label": "A", "text": "Auriculotemporal Nerve", "correct": false}, {"label": "B", "text": "Auricular branch of vagus nerve", "correct": true}, {"label": "C", "text": "Greater Auricular Nerve", "correct": false}, {"label": "D", "text": "Facial Nerve", "correct": false}], "correct_answer": "B. Auricular branch of vagus nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Auricular branch of vagus nerve Cough reflex is due to the auricular branch of vagus nerve/Arnold’s nerve . Mechanical stimulation of the external auditory meatus can activate the auricular branch of the vagus nerve (Arnold’s nerve) and evoke reflex cough .</p>\n<p><strong>Extraedge:</strong></p><p>It is necessary before syringing to ask the patient for any past history of ear discharge or an existing perforation. A quiescent otitis media may be reactivated by syringing.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 35-year-old woman comes to the outpatient department of ENT and presents with a facial weakness. On examination, she has vesicles in the external auditory canal and over the tympanic membrane. What is the diagnosis?", "options": [{"label": "A", "text": "Horner’s Syndrome", "correct": false}, {"label": "B", "text": "Ramsay Hunt Syndrome", "correct": true}, {"label": "C", "text": "Charge Syndrome", "correct": false}, {"label": "D", "text": "Gradenigo Syndrome", "correct": false}], "correct_answer": "B. Ramsay Hunt Syndrome", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ramsay Hunt Syndrome The given clinical scenario of facial paralysis along with vesicular rash in the EAC and TM is suggestive of Ramsay Hunt syndrome/ herpes zoster oticus .</p>\n<p><strong>Highyeild:</strong></p><p>Ramsay Hunt Syndrome It is due to the herpes zoster infection of the geniculate ganglion. There is facial paralysis along with vesicular rash in the external auditory canal and pinna. Patients present with intense pain around the ear in addition to the vesicles. Ramsay-Hunt syndrome. Note facial palsy and small vesicles in the concha of the right side.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Horner syndrome includes ptosis, miosis, and anhidrosis. Option: C. CHARGE syndrome includes coloboma, heart defects, atresia choanae, growth retardation, genital abnormalities, and ear abnormalities. Option: D. Gradenigo syndrome includes the triad of external rectus palsy, deep-seated ear pain, and persistent ear discharge.</p>\n<p><strong>Extraedge:</strong></p><p>There may also be anesthesia of the face, giddiness, and hearing impairment due to the involvement of the Vth and VIIIth nerves. Treatment is the same as for Bell's palsy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 17-year-old kushti player presented with an increasingly painful left ear swelling of 5 days duration. He denied any procedure done on the ear. The image of the ear is shown below. What’s the diagnosis?", "options": [{"label": "A", "text": "Haematoma", "correct": true}, {"label": "B", "text": "Abscess", "correct": false}, {"label": "C", "text": "Seroma", "correct": false}, {"label": "D", "text": "Sebaceous Cyst", "correct": false}], "correct_answer": "A. Haematoma", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994656360-QTDE009020IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Haematoma As the patient is a kushti player so there are chances of trauma to the ear. The image shown confirms ear hematoma/ cauliflower/boxer’s ear.</p>\n<p><strong>Highyeild:</strong></p><p>Haematoma of the auricle is the collection of blood between the auricular cartilage and its perichondrium. It is the result of blunt trauma seen in boxers, wrestlers, and rugby players. Extravasated blood may clot and then organize, resulting in a typical deformity called Cauliflower ear (pugilistic or boxer’s ear). If hematoma gets infected, severe perichondritis may set in. Cauliflower ear (pugilistic or boxer's ear).</p>\n<p><strong>Extraedge:</strong></p><p>Treatment is the aspiration of the hematoma under strict aseptic precautions and a pressure dressing, carefully packing all concavities of the auricle to prevent reaccumulation. Aspiration may need to be repeated. When aspiration fails, incision and drainage should be done and pressure applied by dental rolls tied with through and through sutures. All cases should receive prophylactic antibiotics.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Identify the condition:", "options": [{"label": "A", "text": "Cauliflower Ear", "correct": false}, {"label": "B", "text": "Perichondritis", "correct": true}, {"label": "C", "text": "Chondrodermatitis nodularis chronica helicis", "correct": false}, {"label": "D", "text": "Cup ear", "correct": false}], "correct_answer": "B. Perichondritis", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994656625-QTDE009021IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Perichondritis As the pinna is red, hot, and painful and feels stiff, it is perichondritis .</p>\n<p><strong>Highyeild:</strong></p><p>Perichondritis It results from infection secondary to lacerations, hematoma, or surgical incisions. It can also result from an extension of infection from diffuse otitis externa or a furuncle of the meatus. Pseudomonas and mixed flora are the common pathogens. Initial symptoms are red, hot, and painful pinna which feels stiff. Later abscess may form between the cartilage and perichondrium with necrosis of cartilage as the cartilage survives only on the blood supply from its perichondrium.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Cauliflower ear/ boxer’s ear is a hematoma of the auricle seen in wrestlers. Option: C. Chondrodermatitis nodularis chronica helicis are small painful nodules that appear near the free border of the helix in men about the age of 50 years. Nodules are tender and the patient is unable to sleep on the affected side. Option: D. Cup ear is a hypoplasia of the upper third of the auricle. The upper portion of the helix or pinna is cupped.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment in the early stages consists of systemic antibiotics and the local application of 4% aluminum acetate compresses. When an abscess has formed, it must be drained promptly, and culture and sensitivity of the pus obtained. Incision is made in the natural fold and devitalized cartilage is removed.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The true statement about malignant otitis externa is", "options": [{"label": "A", "text": "Not Painful", "correct": false}, {"label": "B", "text": "Common in diabetics or old age", "correct": true}, {"label": "C", "text": "Caused by Streptococcus", "correct": false}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "B. Common in diabetics or old age", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Common in diabetics or old age Malignant otitis externa is common in immunocompromised patients such as diabetics, and patients on radiotherapy.</p>\n<p><strong>Highyeild:</strong></p><p>Malignant (necrotizing) otitis externa is an inflammatory condition caused by pseudomonas infection usually in the elderly, diabetics, or those on immunosuppressive drugs . It is not a tumor. Clinical feature- Severe otalgia in an elderly diabetic patient with granulation tissue in the external ear canal Gallium scan- for prognosis and response to treatment.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Malignant otitis externa is a painful condition. Option: C. Malignant otitis externa is most commonly caused by pseudomonas.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is true about the image?", "options": [{"label": "A", "text": "It is frequently associated with anomalies of the external auditory canal, middle and internal ear", "correct": true}, {"label": "B", "text": "It is the hypoplasia of the upper third of the auricle", "correct": false}, {"label": "C", "text": "Upper third of the auricle is embedded in scalp skin", "correct": false}, {"label": "D", "text": "It is the complete absence of pinna", "correct": false}], "correct_answer": "A. It is frequently associated with anomalies of the external auditory canal, middle and internal ear", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994656817-QTDE009024IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>It is frequently associated with anomalies of the external auditory canal, middle and internal ear The given image shows a case of microtia . It is also known as peanut ear . Microtia is usually seen as anomalies associated with the external auditory canal, middle and internal ear.</p>\n<p><strong>Highyeild:</strong></p><p>Microtia right ear (peanut ear).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: B. Hypoplasia of the upper third of the auricle is called a cup ear. The upper portion of the helix or pinna is cupped Option: C. Condition with the Upper third of the auricle embedded under the scalp skin is seen in cryptotia. It is also known as pocket ear Option: D. Complete absence of pinna is known as anotia.</p>\n<p><strong>Extraedge:</strong></p><p>It is frequently associated with anomalies of the external auditory canal and middle and internal ear. The condition may be unilateral or bilateral. Hearing loss is frequent. Peanut ear is a form of microtia.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following statement is false regarding the trauma of the auricle?", "options": [{"label": "A", "text": "Completely avulsed pinna can be reimplanted", "correct": false}, {"label": "B", "text": "Skin is closed with absorbable sutures in case of laceration", "correct": true}, {"label": "C", "text": "Aspiration is the treatment for boxer’s ear", "correct": false}, {"label": "D", "text": "There is a risk for avascular necrosis in cases of laceration of pinna", "correct": false}], "correct_answer": "B. Skin is closed with absorbable sutures in case of laceration", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Skin is closed with absorbable sutures in case of laceration Skin is closed with fine non absorbable sutures in case of laceration . The Perichondrium of the pinna must be closed with absorbable sutures.</p>\n<p><strong>Highyeild:</strong></p><p>Laceration left pinna.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Completely avulsed pinna can be re-implanted in selected cases with the help of microvascular techniques. Option: C. Aspiration is the treatment for the boxer’s ear. Aspiration along with pressure dressing is done in cases of hematoma. If required incision and drainage are also done. Option: D. There is a risk of avascular necrosis in case of laceration of the pinna. Avascular necrosis can be due to the stripping of perichondrium from cartilage.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 20-year-old male presented with a pus-filled skin lesion with severe pain and tenderness which was out of proportion to the size of the lesion. What is your most probable diagnosis?", "options": [{"label": "A", "text": "Otomycosis", "correct": false}, {"label": "B", "text": "Furuncle", "correct": true}, {"label": "C", "text": "Diffuse Otitis Externa", "correct": false}, {"label": "D", "text": "Otitis Haemorrhagic", "correct": false}], "correct_answer": "B. Furuncle", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Furuncle Furuncle is a staphylococcal infection of the hair follicle As the hair is confined only to the cartilaginous part of the meatus Furuncle is only seen in this part of the meatus It usually presents as a severe pain and tenderness which is out of proportion</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Otomycosis The clinical features include intense itching Discomfort or pain in the ear, watery discharge, musty odour and ear blockage Option: C. Diffuse otitis externa It is a diffuse inflammation of meatal skin which may spread to involve the pinna and epidermal layer of tympanic membrane It presents as a hot burning sensation in the ear Option: D. Otitis haemorrhagic It is characterised by the formation of haemorrhagic bullae on tympanic membrane And its probably viral in origin</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is false regarding furuncle?", "options": [{"label": "A", "text": "The most common causative organism is staphylococcus", "correct": false}, {"label": "B", "text": "10% ichthammol glycerine is used in treatment", "correct": false}, {"label": "C", "text": "Periauricular lymph nodes will be enlarged", "correct": false}, {"label": "D", "text": "Furuncle can be seen all along the ear canal", "correct": true}], "correct_answer": "D. Furuncle can be seen all along the ear canal", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Furuncle can be seen all along the ear canal Furuncle is a staphylococcal infection of the hair follicle. As the hair is confined only to the cartilaginous part of the meatus furuncle is usually seen only in this part of the ear canal .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Staphylococcus is the most common organism which causes furuncle. Option: B. 10% ichthammol glycerine is used in treatment. Hygroscopic action of glycerine reduces oedema. While ichthammol is mildly antiseptic. Option: C. periauricular lymph nodes may be enlarged and tender.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 40-year-old male who goes swimming thrice a week regularly presented with a hot burning sensation of the ear. He also complains of pain which is aggravated by movements of the jaw. Identify the false statement regarding the condition the patient presents:", "options": [{"label": "A", "text": "Acute phase there will be ear discharge", "correct": false}, {"label": "B", "text": "The patient will have conductive hearing loss", "correct": false}, {"label": "C", "text": "Excessive sweating changes the ph of meatal skin from alkaline to acid", "correct": true}, {"label": "D", "text": "Chronic phase is characterised by irritation and itching", "correct": false}], "correct_answer": "C. Excessive sweating changes the ph of meatal skin from alkaline to acid", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Excessive sweating changes the ph of meatal skin from alkaline to acid Diagnosis is diffuse otitis externa. It is usually seen in swimmers and in hot and humid climates. Excessive sweating changes the ph of meatal skin from acid to alkaline ; not from alkaline to acid. Which favours the growth of pathogens.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. In the Acute phase, there will be serious ear discharge. Option: B. The patient will have conductive hearing loss. Collection of debris and discharge accompanied by meatal swelling gives rise to conductive hearing loss. Option: D. Chronic phase is characterised by irritation and itching. This is responsible for acute exacerbation and reinfection.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "False statement regarding diffuse otitis externa:", "options": [{"label": "A", "text": "Diffuse inflammation involving the epidermal layer", "correct": true}, {"label": "B", "text": "The discharge is purulent in nature", "correct": false}, {"label": "C", "text": "In the chronic phase, the discharge will be scanty", "correct": false}, {"label": "D", "text": "Trauma to the ear is not an etiological factor", "correct": false}], "correct_answer": "A. Diffuse inflammation involving the epidermal layer", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Diffuse inflammation involving the epidermal layer Trauma to the metal skin is the most common factor responsible for diffuse otitis externa. Trauma can result from scratching the ear canal with hair pins or matchsticks, or unskilled instrumentation.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. It is a diffuse inflammation of meatal skin which may spread to involve the meatal pinna and epidermal layer of the tympanic membrane. Option: B . In the acute phase of the disease initially, there will be a thin serous discharge which will become thick and purulent in a later stage. Option: C. In the chronic phase, the discharge will be scanty and may dry up to form crusts.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 32-year-old male presented with a white-coloured lesion with intense itching, discomfort, watery discharge and odour. What is your most probable diagnosis?", "options": [{"label": "A", "text": "Furuncle", "correct": false}, {"label": "B", "text": "Malignant Otitis Externa", "correct": false}, {"label": "C", "text": "Otomycosis", "correct": true}, {"label": "D", "text": "Diffuse Otitis Externa", "correct": false}], "correct_answer": "C. Otomycosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Otomycosis It is a fungal infection of the ear canal. The clinical features of otomycosis include intense itching, discomfort or pain in the ear . Watery discharge with a musty odour, and ear blockage . The fungal mass appears as white, black, or brown.</p>\n<p><strong>Highyeild:</strong></p><p>Otomycosis Otomycosis is a fungal infection of the ear canal that often occurs due to Aspergillus niger, A. fu- mitigates or Candida albicans. It is seen in hot and humid climates of tropical and subtropical countries. Secondary fungal growth is also seen in patients using topical antibiotics for the treatment of otitis externa or middle ear suppuration. The clinical features of otomycosis include intense itching, discomfort or pain in the ear, watery discharge with a musty odour and ear blockage.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Furuncle is a staphylococcal infection of the hair follicle Option: B. Malignant otitis externa is an inflammatory condition caused by pseudomonas infection Usually in elderly diabetics and those on immunosuppressive drugs. Clinical features include excruciating pain and granulations in the ear canal Option: D. Diffuse otitis externa is Diffuse inflammation of the meatal skin. Clinical features include a hot burning sensation in the ear, thin serous discharge, and collection of debris in the acute phase in the chronic phase scanty discharge, irritation and a strong desire to itch.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment consists of a thorough ear toilet to remove all discharge and epithelial debris which are conducive to the growth of fungus. It can be done by syringing, suction or mopping. Nystatin (100,000 units/mL of propylene glycol) is effective against Candida. Other broad-spectrum anti-fungal agents include clotrimazole and povidone-iodine. Two per cent salicylic acid in alcohol is also effective. It is a keratolytic agent which removes superficial layers of the epidermis, and along with that, the fungal mycelia grows into them.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 29-year-old female presented with granulations in the ear canal with severe excruciating pain. She also showed signs of facial paralysis. What will be your most probable diagnosis?", "options": [{"label": "A", "text": "Malignant Otitis Externa", "correct": true}, {"label": "B", "text": "Diffuse Otitis Externa", "correct": false}, {"label": "C", "text": "Otitis Haemorrhagica", "correct": false}, {"label": "D", "text": "Otomycosis", "correct": false}], "correct_answer": "A. Malignant Otitis Externa", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Malignant Otitis Externa Granulations in the ear canal along with excruciating pain suggest malignant otitis externa . Facial paralysis is also seen.</p>\n<p><strong>Highyeild:</strong></p><p>Malignant (necrotizing) otitis externa is an inflammatory condition caused by pseudomonas infection usually in the elderly, diabetics, or those on immunosuppressive drugs . It is not a tumor. Clinical feature- Severe otalgia in an elderly diabetic patient with granulation tissue in the external ear canal Gallium scan- for prognosis and response to treatment.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: B. Diffuse otitis externa is a diffuse inflammation of the metal skin. It is commonly seen in hot and humid climates and in swimmers. The acute phase is characterised by a hot burning sensation in the ear, and thin serous discharge Option: C. Otitis haemorrhagic is characterised by the formation of haemorrhagic bullae on the tympanic membrane. It is probably viral in origin. Option: D. Otomycosis is a Fungal infection of the ear canal. It is characterized by intense itching, discomfort or pain in the ear, Musty odour, and ear blockage. Fungal mass will be white, brown or black colour depending on the fungal pathogen.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 19-year-old presented to your opd with severe ear pain, temporary facial paralysis, vertigo, nausea, and vomiting. On examination, the tympanic membrane showed fluid-filled vesicles. What will be your probable diagnosis?", "options": [{"label": "A", "text": "Otitis Externa Haemorrhagic", "correct": false}, {"label": "B", "text": "Herpes Zoster Oticus", "correct": true}, {"label": "C", "text": "Malignant Otitis Externa", "correct": false}, {"label": "D", "text": "Furuncle", "correct": false}], "correct_answer": "B. Herpes Zoster Oticus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Herpes Zoster Oticus Herpes zoster oticus/ Ramsay hunt syndrome is characterised by the formation of vesicles on the tympanic membrane, meatal skin, concha and postauricular groove. The 7th and 8th cranial nerves may also get involved.</p>\n<p><strong>Highyeild:</strong></p><p>Ramsay-Hunt syndrome. Note facial palsy and small vesicles in the concha of the right side.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. In Otitis externa haemorrhagica there is a Formation of haemorrhagic bullae on the tympanic membrane. It is Viral in origin. Option: C. In Malignant otitis externa, there will be granulations in the ear canal and excruciating pain. Option: D. Furuncle is a Staphylococcal infection of the hair follicle of the ear canal Characterised by tenderness and severe pain which is out of proportion.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 25-year-old female presented to your opd with ear pain, itching, and irritation in and around the ear canal and also scaly skin around the lobule and the external canal. which among the following will you think is the strongest association for this condition?", "options": [{"label": "A", "text": "Pseudomonas Infection", "correct": false}, {"label": "B", "text": "Hypersensitivity", "correct": false}, {"label": "C", "text": "Psychological Factors", "correct": false}, {"label": "D", "text": "Seborrhoeic dermatitis of scalp", "correct": true}], "correct_answer": "D. Seborrhoeic dermatitis of scalp", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Seborrhoeic dermatitis of scalp From the above symptoms given we can arrive at a diagnosis of seborrheic otitis externa . Greasy yellow scales are seen around the lobule, ear canal, and postauricular sulcus.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Pseudomonas infection is mainly associated with malignant otitis externa. Option: B. Eczematous otitis externa occurs as a result of hypersensitivity to infective organisms Option: C. Neurodermatitis is a condition that is related to compulsive scratching due to psychological factors.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 40 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 40</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Diseases of External Nose - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 40</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 40 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "All statements are true about CSF rhinorrhoea except the?", "options": [{"label": "A", "text": "Handkerchief sign is seen", "correct": true}, {"label": "B", "text": "T2 weighted MRI is the imaging investigation of choice", "correct": false}, {"label": "C", "text": "Beta 2transferrin is a 100 percent diagnostic method for proving disease entity", "correct": false}, {"label": "D", "text": "Cribriform plate is the most common site of the leak", "correct": false}], "correct_answer": "A. Handkerchief sign is seen", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Handkerchief sign is seen. A handkerchief sign is seen in nasal discharge. In CSF rhinorrhea Halo/target sign is seen.</p>\n<p><strong>Highyeild:</strong></p><p>CSF RHINORRHEA Watery nasal discharge which cannot be sniffed back and increased on bending forwards and Valsalva Target or Halo sign (+) - A drop when collected on a piece of filter paper, produces a central red spot (due to blood) and a peripheral lighter halo around the blood circle (if CSF is present) Reservoir sign : (Done to elicit CSF rhinorrhea) After being supine → the patient is made to sit up in the upright position with the neck flexed. If there is a sudden rush of clear fluid, it indicates CSF rhinorrhea Laboratory tests: Beta-2 transferrin is a protein seen in CSF and not in the nasal discharge. The presence of β2 transferrin is pathognomic for CSF rhinorrhea. The imaging modality of choice : To diagnose the site of the leak T2 weighted MRI</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. T2 weighted MRI is the imaging investigation of choice Option: C. Presence of β2 transferrin is pathognomic for CSF rhinorrhea. Option: D. Cribriform plate is the most common site of leak is a true statement.</p>\n<p><strong>Extraedge:</strong></p><p>Another protein called beta trace protein is also specific for CSF and is widely used in Europe. It is secreted by the meninges and choroid plexus. Facilities to test these proteins are not easily available everywhere. Glucose testing by oxidase peroxidase or biochemical estimation is no longer used.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are true about choanal atresia except", "options": [{"label": "A", "text": "Due to the persistence of bucconasal membrane", "correct": false}, {"label": "B", "text": "Mc govern technique is also used", "correct": false}, {"label": "C", "text": "Surgery by transnasal or trans palatal approach", "correct": false}, {"label": "D", "text": "Bilateral is more common", "correct": true}], "correct_answer": "D. Bilateral is more common", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Bilateral is more common Unilateral choanal atresia is more common than bilateral one.</p>\n<p><strong>Highyeild:</strong></p><p>CHOANAL ATRESIA Congenital disorder due to the persistence of bucconasal membrane. U/L or B /L, complete or incomplete, Bony (90 percent ) or membranous (10 percent ). U / L is more common than B / L. Emergency management may be required in B/ L atresia to provide an airway.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Choanal atresia is a congenital disorder due to the persistence of bucconasal membrane. Option: B. A feeding nipple with a large hole provides a good oral airway (McGovern’s technique). Option: C. Definitive treatment consists of correction of atresia by transnasal or trans palatal approach</p>\n<p><strong>Extraedge:</strong></p><p>Emergency management may be required in bilateral choanal atresia to provide an airway. A feeding nipple with a large hole provides a good oral airway (McGovern’s technique) and obviates the need for tracheostomy. Definitive treatment consists of correction of atresia by transnasal or trans palatal approach. The latter is usually done for one and a half years. Choanal atresia can be corrected by using nasal endoscopes and drills. Removal of a part of the posterior nasal septum transnasally is another option to treat such cases.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are true about antrochoanal polyps except", "options": [{"label": "A", "text": "Also known as Killian polyp", "correct": false}, {"label": "B", "text": "Steroid and Caldwell Luc operation is the mainstay of treatment", "correct": true}, {"label": "C", "text": "FESS is the treatment of choice", "correct": false}, {"label": "D", "text": "Site of Attachment is an anterolateral wall of maxilla.", "correct": false}], "correct_answer": "B. Steroid and Caldwell Luc operation is the mainstay of treatment", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Steroid and Caldwell Luc operation is the mainstay of treatment Steroids are not used as a treatment in antrochoanal polyps. Caldwell Luc operation is done only for recurrent cases .</p>\n<p><strong>Highyeild:</strong></p><p>ANTROCHOANAL POLYP/ KILLIAN’S POLYP Arises from the mucosa of the maxillary antrum near its accessory ostium, comes out of it and grows in the choana and nasal cavity. Symptoms- U/L nasal obstruction is the presenting symptom. Voice may become dull due to hyponasality. Rx: Avulsion, Endoscopic sinus surgery (FESS) superseded all other modalities. Caldwell - Luc was done before but only for recurrent cases. An antrochoanal polyp projecting through the left nostril in a 14-year-old patient.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Antrochoanal polyp is also known as Killian’s polyp. Option: C. FESS is the treatment of choice for antrochoanal polyps. Option: D. Site of Attachment for the antrochoanal polyp is an anterolateral wall of the maxilla.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which cell lies at the upper part of the uncinate process to give access to the frontal sinus?", "options": [{"label": "A", "text": "Bulla Ethmoidalis", "correct": false}, {"label": "B", "text": "Agger Nasi Cell", "correct": true}, {"label": "C", "text": "Haller Cell", "correct": false}, {"label": "D", "text": "Onodi Cell", "correct": false}], "correct_answer": "B. Agger Nasi Cell", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Agger Nasi Cell Uncinate process is a hook-like structure in the middle meatus. Agger nasi is an elevation just anterior to the attachment of the middle turbinate . When pneumatized, it contains air cells , the agger nasi cells which communicate with the frontal recess.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Bulla ethmoidal is an ethmoidal cell situated behind the uncinate process. The anterior surface of the bulla forms the posterior boundary of hiatus semilunaris. Option: C. Haller cells are air cells situated in the roof of the maxillary sinus. They are pneumatized from anterior or posterior ethmoid cells. Option: D. Onodi cell is a posterior ethmoidal cell that may grow posteriorly by the side of the sphenoid sinus or superior to it for as much distance as 1.5 cm from the anterior surface of the sphenoid. Onodi cell is surgically important as the optic nerve may be related to its lateral wall.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are indications of tracheostomy except", "options": [{"label": "A", "text": "Removal of retained secretions", "correct": false}, {"label": "B", "text": "Prolonged Intubation", "correct": false}, {"label": "C", "text": "U/L Choanal Atresia", "correct": true}, {"label": "D", "text": "Bilateral abductor paralysis.", "correct": false}], "correct_answer": "C. U/L Choanal Atresia", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>U/L Choanal Atresia Unilateral choanal atresia is not an indication of tracheostomy.</p>\n<p><strong>Highyeild:</strong></p><p>INDICATIONS FOR TRACHEOSTOMY Respiratory obstruction Infections Acute laryngo-tracheo-bronchitis, acute epiglottitis, diphtheria Ludwig's angina, peritonsillar, retropharyngeal or parapharyngeal abscess, tongue abscess Trauma External injury of larynx and trachea Trauma due to endoscopies, especially in infants and children Fractures of mandible or maxillofacial injuries Neoplasms Foreign body larynx Oedema larynx due to steam, irritant fumes or gases, allergy (angioneurotic or drug sensitivity), radiation Bilateral abductor paralysis Congenital anomalies Laryngeal web, cysts, tracheo-oesophageal fistula Bilateral choanal atresia Retained secretions Inability to cough Coma of any cause, e.g. head injuries, cerebrovascular accidents, narcotic overdose Paralysis of respiratory muscles, e.g. spinal injuries, polio, Guillain-Barre syndrome, myasthenia gravis Spasm of respiratory muscles, tetanus, eclampsia, strychnine poisoning Painful cough Aspiration of pharyngeal secretions Respiratory insufficiency Chronic lung conditions, viz. emphysema, chronic bronchitis, bronchiectasis, atelectasis Conditions listed in A and B</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Removal of retained secretions as in a coma, aspiration is an indication for tracheostomy. Option: B. Prolonged intubation is also an indication of tracheostomy. Option: D. Bilateral abductor paralysis is also an indication of tracheostomy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Rhinolith can cause perforation of nasal septum by", "options": [{"label": "A", "text": "Pressure Necrosis", "correct": true}, {"label": "B", "text": "Malignant Transformation", "correct": false}, {"label": "C", "text": "Autoimmune Reaction", "correct": false}, {"label": "D", "text": "Hypersensitivity", "correct": false}], "correct_answer": "A. Pressure Necrosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Pressure Necrosis Rhinolith is stone formation in the nasal cavity. They may cause pressure necrosis of the septum and/or lateral wall of the nose.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOLITH Rhinolith is a stone formation in the nasal cavity. Over a period of time, it grows into a large, irregular mass that fills the nasal cavity and then may cause pressure necrosis of the septum and/or lateral wall of the nose. Rhinoliths are more common in adults. Common presentation is unilateral nasal obstruction and foul-smelling discharge which is very often bloodstained. On examination, a grey-brown or greenish-black mass with an irregular surface and stony hard feel is seen in the nasal cavity between the septum and turbinates.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Malignant Transformation does not occur in rhinolith. Option: C. Autoimmune Reaction does not occur in rhinolith. Option: D. Hypersensitivity does not occur in rhinolith.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Antrochoanal polyp is?", "options": [{"label": "A", "text": "Single and unilateral", "correct": true}, {"label": "B", "text": "Multiple", "correct": false}, {"label": "C", "text": "Grow anteriorly", "correct": false}, {"label": "D", "text": "None", "correct": false}], "correct_answer": "A. Single and unilateral", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Single and unilateral Usually antrochoanal polyps are single and unilateral.</p>\n<p><strong>Highyeild:</strong></p><p>ANTROCHOANAL POLYP/ KILLIAN’S POLYP Arises from the mucosa of the maxillary antrum near its accessory ostium, comes out of it and grows in the choana and nasal cavity. Symptoms- U/L nasal obstruction is the presenting symptom. Voice may become dull due to hyponasality. Rx: Avulsion, Endoscopic sinus surgery (FESS) superseded all other modalities. Caldwell - Luc was done before but only for recurrent cases. An antrochoanal polyp projecting through the left nostril in a 14-year-old patient.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Antrochoanal polyps are single and not multiple. Option: C. Antrochoanal polyps grow posteriorly and not anteriorly. That’s why they have usually missed n anterior rhinoscopy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Maggots in the nasal cavity are most commonly treated by:", "options": [{"label": "A", "text": "Chloroform diluted with water", "correct": true}, {"label": "B", "text": "Liquid Paraffin", "correct": false}, {"label": "C", "text": "Systemic Antibiotics", "correct": false}, {"label": "D", "text": "Lignocaine Spray", "correct": false}], "correct_answer": "A. Chloroform diluted with water", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Chloroform diluted with water All visible maggots should be picked up with forceps. Many of them try to retreat into darker cavities when light falls on them. Installation of chloroform water and oil kills them.</p>\n<p><strong>Highyeild:</strong></p><p>(A) The maggot. (B) The fly responsible for maggots.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option:B. Liquid Paraffin is not used for maggots in the nasal cavity. Option:C . Systemic Antibiotics are not used for maggots in the nasal cavity. Option:D. Lignocaine Spray is not used for maggots in the nasal cavity.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The most common cause of oroantral fistula:", "options": [{"label": "A", "text": "Tb", "correct": false}, {"label": "B", "text": "Penetrating Injury", "correct": false}, {"label": "C", "text": "Tooth Extraction", "correct": true}, {"label": "D", "text": "Latrogenic", "correct": false}], "correct_answer": "C. Tooth Extraction", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tooth Extraction Dental extraction is the most common cause of oroantral fistula, especially extraction of roots of the second premolar and upper molars.</p>\n<p><strong>Highyeild:</strong></p><p>OROANTRAL FISTULA Oroantral fistula is a communication between the antrum and oral cavity. AETIOLOGY Dental extraction is the most important cause. Roots of the second premolar and upper molars Failure of sublabial incision to heal after Caldwell-Luc operation. Erosion of antrum by carcinoma. Fractures or penetrating injuries of the maxilla. Osteitis of maxilla, syphilis or malignant granuloma.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A Patient after septal surgery complains of nasal obstruction and the following is seen on the anterior rhinoscopy. The most appropriate Treatment for the condition is", "options": [{"label": "A", "text": "Incision & Drainage", "correct": false}, {"label": "B", "text": "Needle Aspiration", "correct": false}, {"label": "C", "text": "Systemic Antibiotics", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996212319-QTDE010011IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above The given Image and history say that it is a case of a septal haematoma . Small haematomas can be aspirated with a wide-bore sterile needle . Larger haematomas are incised and drained by a small anteroposterior incision parallel to the nasal floor . Systemic antibiotics should be given to prevent septal abscesses.</p>\n<p><strong>Highyeild:</strong></p><p>SEPTAL HAEMATOMA It is a collection of blood under the perichondrium or periosteum of the nasal septum. It often results from nasal trauma or septal surgery. Bilateral nasal obstruction is the commonest presenting symptom. Examination reveals smooth rounded swelling of the septum in both the nasal fossae Small hematomas can be aspirated with a wide-bore sterile needle. Larger hematomas are incised and drained by a small anteroposterior incision parallel to the nasal floor. Following drainage, the nose is packed on both sides to prevent reaccumulation. Systemic antibiotics should be given to prevent septal abscesses.</p>\n<p><strong>Extraedge:</strong></p><p>Small haematomas can be aspirated with a wide-bore sterile needle . Larger haematomas are incised and drained by a small anteroposterior incision parallel to the nasal floor . Systemic antibiotics should be given to prevent septal abscesses.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Most common bacterial sinusitis in children:", "options": [{"label": "A", "text": "Ethmoidal", "correct": true}, {"label": "B", "text": "Frontal", "correct": false}, {"label": "C", "text": "Maxillary", "correct": false}, {"label": "D", "text": "Sphenoid", "correct": false}], "correct_answer": "A. Ethmoidal", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ethmoidal Ethmoid and Maxillary sinuses are the most common sinuses involved in younger children. Ethmoid sinusitis > Maxillary sinusitis</p>\n<p><strong>Highyeild:</strong></p><p>ETHMOIDAL SINUS Well developed at birth Reaches adult size by 12 years Most pneumatized at birth hence M/C sinusitis in children Clinically ethmoid cells are divided by the basal lamina into anterior ethmoid group which opens into middle meatus and posterior ethmoid group which opens into superior meatus</p>\n<p><strong>Extraedge:</strong></p><p>Ant group includes cells : (a) Ager nassi cells (b) Ethmoidal bulla (c) Supraorbital cells (d) Fronto-ethmoid cells (e) Haller cells The posterior group includes - onodi cells X-ray: visible at 1st year of age and complete by puberty Most common cause of acute sinusitis in children.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Bent and Kuhn's diagnostic major criteria for allergic fungal Rhinosinusitis include all except", "options": [{"label": "A", "text": "Fungal positive stain", "correct": false}, {"label": "B", "text": "CT Scan Findings", "correct": false}, {"label": "C", "text": "Nasal Polyposis", "correct": false}, {"label": "D", "text": "serum eosinophilia", "correct": true}], "correct_answer": "D. serum eosinophilia", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>serum eosinophilia Serum eosinophilia is a minor criterion for diagnosis of allergic fungal rhinosinusitis.</p>\n<p><strong>Highyeild:</strong></p><p>Bent and Kuhn Diagnostic Criteria Used in diagnosis of Allergic fungal Rhinosinusitis Bent and Kuhn Diagnostic Criteria Major Minor Type I hypersensitivity Nasal polyposis Characteristic CT findings Eosinophilic mucin without invasion Positive fungal stain Asthma Unilateral disease Bone erosion Fungal cultures Charcot-Leyden crystals Serum eosinophilia CT, computed tomography</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option:A. Fungal positive stain is a major criterion for diagnosis of allergic fungal rhinosinusitis. Option:B. Characteristic CT Scan Findings are also a major criterion for diagnosis of allergic fungal rhinosinusitis. Option:C. Nasal Polyposis is also a major criterion for diagnosis of allergic fungal rhinosinusitis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45-year diabetic pt with uncontrolled sugar status came in OPD with c/o pain in the maxillary region. On examination, black nasal crusts were seen in the nasal cavity with the destruction of turbinates. The drug of choice for the patient is", "options": [{"label": "A", "text": "Third Generation Cephalosporins", "correct": false}, {"label": "B", "text": "Metrogyl", "correct": false}, {"label": "C", "text": "Amphotericin B", "correct": true}, {"label": "D", "text": "Ampicillin", "correct": false}], "correct_answer": "C. Amphotericin B", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Amphotericin B Diabetic patient (immunocompromised) along with black crusts seen in the nasal cavity is suggestive of rhinocerebral mucormycosis . The drug of choice for mucormycosis is Amphotericin B.</p>\n<p><strong>Highyeild:</strong></p><p>MUCORMYCOSIS It is a fungal infection of the nose and paranasal sinuses. seen in uncontrolled diabetics or in those taking immunosuppressive drugs. The rapid destruction associated with the disease is due to the affinity of the fungus to invade the arteries and cause endothelial damage and thrombosis. Typical finding is the presence of a black necrotic mass filling the nasal cavity and eroding the sep- tum and hard palate.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment is by amphotericin B and surgical debridement of the affected tissues and control of underlying predisposing cause.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Alkaline nasal douche includes all except", "options": [{"label": "A", "text": "Na Biborate", "correct": false}, {"label": "B", "text": "Glucose", "correct": true}, {"label": "C", "text": "NACL", "correct": false}, {"label": "D", "text": "Nahco3", "correct": false}], "correct_answer": "B. Glucose", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Glucose Alkaline nasal douche doesn’t have glucose in it. These are used to remove crusts in the nose.</p>\n<p><strong>Highyeild:</strong></p><p>Alkaline nasal douche composition: Sodium biborate Sodium bicarbonate Sodium Chloride Luke warm water</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are true about juvenile nasopharyngeal angiofibroma except", "options": [{"label": "A", "text": "Most common in adolescent females", "correct": true}, {"label": "B", "text": "Arise from sphenopalatine foramen", "correct": false}, {"label": "C", "text": "Presents with epistaxis and nasal obstruction", "correct": false}, {"label": "D", "text": "CT shows anterior bowing of posterior wall of nasopharynx", "correct": false}], "correct_answer": "A. Most common in adolescent females", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Most common in adolescent females Juvenile nasopharyngeal angiofibroma is more common in adolescent males in the 2nd decade of life.</p>\n<p><strong>Highyeild:</strong></p><p>JUVENILE NASOPHARYNGEAL ANGIOFIBROMA It Is non – a capsulated, Benign fast growing (Aggressive) and vascular tumor of the Nasopharynx, that is only seen in Juvenile males MC site - Sphenopalatine foramen Presentation A juvenile male with severe and recurrent epistaxis (artery supplying tumorMaxillary artery/Sphenopalatine artery) Cheek swelling Signs Antral sign/Holman-Miller sign : Anterior bowing of posterior maxillary wall Hondusa sign : Increase distance between maxilla and mandible Frog face deformity : Tumor is in orbit and Ethmoid - increases distance between eye and nose and proptosis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. The most common site from where Juvenile nasopharyngeal angiofibroma arises is the sphenopalatine foramen. Option: C. Present with epistaxis and nasal obstruction is a true statement. Option: D. CT shows anterior bowing of posterior wall of nasopharynx i.e. antral sign is a true statement.</p>\n<p><strong>Extraedge:</strong></p><p>Angiofibroma. Section shows multiple dilated vessels surrounded by fibrous stroma. (A) H&E, X100. (B) H&E, X200.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Investigation of choice for CSF rhinophores is?", "options": [{"label": "A", "text": "T1 Weighted MRI", "correct": false}, {"label": "B", "text": "T2 Weighted Mri", "correct": true}, {"label": "C", "text": "CT Scan", "correct": false}, {"label": "D", "text": "Radio nucleotide study", "correct": false}], "correct_answer": "B. T2 Weighted Mri", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>T2 Weighted Mri Investigation of choice for CSF rhinorrhea is T2 weighted MRI. T2 MRI is done to study the pathology of the tissues.</p>\n<p><strong>Highyeild:</strong></p><p>CSF RHINORRHEA Watery nasal discharge which cannot be sniffed back and increased on bending forwards and Valsalva Target or Halo sign (+) - A drop when collected on a piece of filter paper, produces a central red spot (due to blood) and a peripheral lighter halo around the blood circle (if CSF is present) Reservoir sign : (Done to elicit CSF rhinorrhea) After being supine → the patient is made to sit up in the upright position with the neck flexed. If there is a sudden rush of clear fluid, it indicates CSF rhinorrhea Laboratory tests: Beta-2 transferrin is a protein seen in CSF and not in the nasal discharge. The presence of β2 transferrin is pathognomic for CSF rhinorrhea. Imaging modality of choice : To diagnose the site of leak is T2 weighted MRI</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The most common presentation of infants with bilateral choanal atresia:", "options": [{"label": "A", "text": "Difficulty in breathing", "correct": true}, {"label": "B", "text": "Dysphagia", "correct": false}, {"label": "C", "text": "Smiling", "correct": false}, {"label": "D", "text": "Difficulty in walking", "correct": false}], "correct_answer": "A. Difficulty in breathing", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Difficulty in breathing Choanal atresia is usually U/L . If it occurs bilaterally the neonate presents with difficulty in breathing as the infant is a nose breather and does not breathe from mouth.</p>\n<p><strong>Highyeild:</strong></p><p>CHOANAL ATRESIA Congenital disorder due to persistence of bucconasal membrane. U/L or B /L, complete or incomplete, Bony (90 percent ) or membranous (10 percent ). U / L is more common than B / L. Emergency management may be required in B/ L atresia to provide an airway.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Thudiculum’s nasal speculum is used to visualize:", "options": [{"label": "A", "text": "Anterior nasal cavity", "correct": true}, {"label": "B", "text": "Posterior Nares", "correct": false}, {"label": "C", "text": "Tonsils", "correct": false}, {"label": "D", "text": "Larynx", "correct": false}], "correct_answer": "A. Anterior nasal cavity", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Anterior nasal cavity Anterior rhinoscopy is done using the thudiculum’s speculum or Vienna-type speculum. Used to visualize the nasal cavity, septum, floor of the nose, inferior and middle turbinate, and inferior and middle meatuses can be visualized by it.</p>\n<p><strong>Highyeild:</strong></p><p>(A) Anterior rhinoscopy. (B) Technique of holding a Thudicum nasal speculum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Alternative for Submucosal resection ?", "options": [{"label": "A", "text": "Tympanoplasty", "correct": false}, {"label": "B", "text": "Septoplasty", "correct": true}, {"label": "C", "text": "Caldwell-Luc Operation", "correct": false}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "B. Septoplasty", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Septoplasty Septoplasty is a Conservative surgery done for deviated nasal septum as most of the septal framework is retained. Only the most deviated parts are removed . The rest of the septal framework is corrected and reposited by plastic means. It is the preferred operation over submucosal resection.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. It is an operation to eradicate the disease in the middle ear and to reconstruct the hearing mechanism. Option: C. Caldwell –Luc operation is a process of opening the maxillary antrum through the canine fossa by sublabial approach and dealing with the pathology inside the antrum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Killian's incision is used for:", "options": [{"label": "A", "text": "Submucous resection of nasal septum", "correct": true}, {"label": "B", "text": "Myringoplasty", "correct": false}, {"label": "C", "text": "Caldwell-Luc Operation", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "A. Submucous resection of nasal septum", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Submucous resection of nasal septum In Submucous Resection of nasal septum ; apart from a thin dorsal and caudal strip, the rest of the entire septum is removed . Incision given For submucous resection is Killian incision given at 25 cm Behind the columella at the mucocutaneous junction at the convex side of the deviation.</p>\n<p><strong>Highyeild:</strong></p><p>Septal incisions. (A) Killian's incision. (B) Hemitransfixion incision</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Closure of perforation of pars tensa of the tympanic membrane is called myringoplasty Option: C. Caldwell–Luc operation is a process of opening the maxillary antrum through canine fossa by sublabial approach and dealing with the pathology inside the antrum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Kashima operation is done for:", "options": [{"label": "A", "text": "Recurrent Cholesteatoma", "correct": false}, {"label": "B", "text": "Bilateral vocal cord palsy", "correct": true}, {"label": "C", "text": "Atrophic Rhinitis", "correct": false}, {"label": "D", "text": "All of the above.", "correct": false}], "correct_answer": "B. Bilateral vocal cord palsy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Bilateral vocal cord palsy Kashima Operation is another name for cordotomy surgery done in case of B/L vocal cord paralysis(B/L abductor paralysis)</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In cholesteatoma, a modified radical mastoidectomy is done. Option: C. Young’s operation is done for atrophic rhinitis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "This elderly patient presented with a slowly enlarging swelling involving his nose. There was occasional itchiness but no obvious pain noted:", "options": [{"label": "A", "text": "Rodent Ulcer", "correct": true}, {"label": "B", "text": "Squamous Cell Carcinoma", "correct": false}, {"label": "C", "text": "Melanoma", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "A. Rodent Ulcer", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996212338-QTDE010026IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rodent Ulcer Basal cell carcinoma (rodent ulcer) is the most common malignant tumor involving the skin of the nose (87%), equally affecting males and females in the age group of 40–60 years . Common sites on the nose are the tip and the ala . It may present as a cyst or papule-pearly nodule or an ulcer with rolled edges . It is very slow growing and remains confined to the skin for a long time.</p>\n<p><strong>Highyeild:</strong></p><p>BASAL CELL CARCINOMA/RODENT ULCER Basal cell carcinoma (rodent ulcer) is the most common malignant tumor involving the skin of the nose (87%) Equally affects males and females in the age group of 40–60 years. Common sites on the nose are the tip and the ala. may present as a cyst or papule-pearly nodule or an ulcer with rolled edges. very slow growing and remains confined to the skin for a long time. Basal cell carcinoma of the nose.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Squamous cell carcinoma is the second most common malignant tumour (11%), equally affecting both sexes in the 40–60 age group. It occurs as an infiltrating nodule or an ulcer with rolled-out edges affecting the side of the nose or columella O ption: C. Melanoma is the least common variety. Clinically, it is a superficially spreading type (slow-growing) or nodular invasive type</p>\n<p><strong>Extraedge:</strong></p><p>Treatment depends on the size, location, and depth of the tumour. Early lesions can be cured by cryosurgery, irradiation, or surgical excision with 3–5 mm of healthy skin around the palpable borders of the tumour. Lesions that are recurrent, extensive, or with involvement of cartilage or bone are excised and the surgical defect is closed by local or distant flaps or a prosthesis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "What is the causative agent of the disease shown below?", "options": [{"label": "A", "text": "Human Papilloma Virus", "correct": false}, {"label": "B", "text": "Herpes Zoster Virus", "correct": true}, {"label": "C", "text": "Herpes Simplex Virus", "correct": false}, {"label": "D", "text": "Ebstein Bar Virus", "correct": false}], "correct_answer": "B. Herpes Zoster Virus", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996212375-QTDE010027IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Herpes Zoster Virus The given Image shows cutaneous eruptions near the ear . In herpes zoster oticus , lesions are seen in the distribution of a facial nerve , i.e. concha, posterior part of the tympanic membrane, and postauricular region.</p>\n<p><strong>Highyeild:</strong></p><p>HERPES ZOSTER OTICUS It is a viral infection involving the geniculate ganglion of the facial nerve. It is characterized by the formation of vesicles on the tympanic membrane, metal skin, concha, and postauricular groove. The VIIth and VIIIth cranial nerves may be involved. If there is facial paralysis along with vesicular rash in the external auditory canal and pinna - Ramsay hunt syndrome Ramsay-Hunt syndrome. Note facial palsy and small vesicles in the concha of the right side.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are indications of the below-given speculum except:", "options": [{"label": "A", "text": "Magnification", "correct": false}, {"label": "B", "text": "Assessment of movement of the tympanic membrane", "correct": false}, {"label": "C", "text": "Removal of foreign body from the ear", "correct": true}, {"label": "D", "text": "As applicator for the powdered antibiotic to ear", "correct": false}], "correct_answer": "C. Removal of foreign body from the ear", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996212385-QTDE010028IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Removal of foreign body from the ear The given Image is of Siegle’s speculum . It does not help in the removal of foreign bodies from the ear.</p>\n<p><strong>Highyeild:</strong></p><p>SIEGEL’S SPECULUM Essential in the examination of the tympanic membrane; it gives a magnified view of the tympanic membrane and helps to test its mobility. It is also used to elicit the fistula sign. Also used to instill antibiotics. Use of Siegel's speculum to see the mobility of the tympanic membrane.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Siegel’s speculum gives a magnified view of the tympanic membrane. Option: B. Siegel’s speculum also helps in the assessment of the movement of the tympanic membrane Option: D. Siegel’s speculum is also used to instill antibiotics.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 30-year-old male comes to the OPD because of a 4-week history of a whistling noise during normal spontaneous respiration. He underwent a difficult rhinoplasty a few months ago. The noise is getting louder and is annoying. On examination, the patient is afebrile. Their past medical history is significant for the presence of atopic dermatitis a few years ago, well controlled after consultation with his dermatologist. Which of the following is the most likely diagnosis?", "options": [{"label": "A", "text": "Nasal Septal Perforation", "correct": true}, {"label": "B", "text": "Nasal Furunculosis", "correct": false}, {"label": "C", "text": "Nasal Foreign Body", "correct": false}, {"label": "D", "text": "Allergic Rhinitis", "correct": false}], "correct_answer": "A. Nasal Septal Perforation", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Nasal Septal Perforation Following nasal surgery , septal perforation is typically the result of a septal hematoma through a septal abscess may also be the cause. Because of the poor regenerating capacity of the septal cartilage, trauma or surgery on the septum may result in septal perforation. The typical postoperative presentation is a whistling noise heard during respiration.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Nasal furunculosis results from staphylococcal folliculitis following nose picking or nasal hair plucking. Option: C. Foreign bodies are common in children. On presentation, patients will have nasal obstruction and may have a foul odor, halitosis, and nasal bleeding. Option: D. Allergic rhinitis commonly presents with rhinorrhea, nasal pruritus, cough, and occasionally dyspnea. On examination, the nasal mucosa is edematous and pale, and polyps may be present.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 27 year old male who was involved in a fistfight after excessive drinking was brought to the casualty. Apart from a 1*2 cm laceration on his right cheek, the below finding is seen. This is treated best with", "options": [{"label": "A", "text": "Incision/ Drainage", "correct": true}, {"label": "B", "text": "Antibiotics", "correct": false}, {"label": "C", "text": "Nasal Packing", "correct": false}, {"label": "D", "text": "Decongestants", "correct": false}], "correct_answer": "A. Incision/ Drainage", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996212395-QTDE010031IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Incision/ Drainage Given case history and findings point towards the diagnosis of septal hematoma along with other minor injuries secondary to a fist fight. If a septal hematoma is present, the hematoma should be incised, drained , and packed to prevent recurrence.</p>\n<p><strong>Highyeild:</strong></p><p>SEPTAL HAEMATOMA It is a collection of blood under the perichondrium or periosteum of the nasal septum. It often results from nasal trauma or septal surgery. Bilateral nasal obstruction is the commonest presenting symptom. Examination reveals smooth rounded swelling of the septum in both the nasal fossae Small hematomas can be aspirated with a wide-bore sterile needle. Larger hematomas are incised and drained by a small anteroposterior incision parallel to the nasal floor. Following drainage, the nose is packed on both sides to prevent reaccumulation. Systemic antibiotics should be given to prevent septal abscesses.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Systemic antibiotics should be given to prevent septal abscess. But first, hmeatoma should be incised and drained. Option: C. Following drainage, the nose is packed on both sides to prevent reaccumulation. Option: D. Decongestants have no role in the treatment of septal hematoma.</p>\n<p><strong>Extraedge:</strong></p><p>Small haematomas can be aspirated with a wide-bore sterile needle . Larger haematomas are incised and drained by a small anteroposterior incision parallel to the nasal floor . Systemic antibiotics should be given to prevent septal abscesses.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 27 year old male who was involved in a fistfight after excessive drinking was brought to the casualty. Apart from a laceration on his forehead, the below finding is seen. True about this condition is:", "options": [{"label": "A", "text": "Occurs due to trauma", "correct": false}, {"label": "B", "text": "Can lead to saddle-nose deformity", "correct": false}, {"label": "C", "text": "Conservative Treatment", "correct": false}, {"label": "D", "text": "Both a and b", "correct": true}], "correct_answer": "D. Both a and b", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996212428-QTDE010032IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Both a and b Given case history and findings point towards the diagnosis of septal hematoma along with other minor injuries secondary to a fist fight. Occurs due to trauma; Can lead to saddle nose deformity May lead to abscess formation</p>\n<p><strong>Highyeild:</strong></p><p>SEPTAL HAEMATOMA It is collection of blood under the perichondrium or periosteum of the nasal septum. It often results from nasal trauma or septal surgery. Bilateral nasal obstruction is the commonest presenting symptom. Examination reveals smooth rounded swelling of the septum in both the nasal fossae Small haematomas can be aspirated with a wide-bore sterile needle. Larger hematomas are incised and drained by a small anteroposterior incision parallel to the nasal floor. Following drainage, the nose is packed on both sides to prevent reaccumulation. Systemic antibiotics should be given to prevent septal abscesses.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: C. Septal haematoma is an emergency and it should be immediately incised and drained; should not be managed conservatively.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 12 year old boy presents with a history of frequent nasal bleeding. His Hb was found to be 6.7 g/d the L and peripheral smear showed normocytic hypochromic anemia. The most probable diagnosis is", "options": [{"label": "A", "text": "Juvenile Nasopharyngeal Angiofibroma", "correct": true}, {"label": "B", "text": "Hematoma", "correct": false}, {"label": "C", "text": "Antrochonal Polyp", "correct": false}, {"label": "D", "text": "Carcinoma of Nasopharynx", "correct": false}], "correct_answer": "A. Juvenile Nasopharyngeal Angiofibroma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Juvenile Nasopharyngeal Angiofibroma A 12 year old boy presents with a history of frequent nasal bleeding with Hb found to be 6.7 g/dL and peripheral smear showing normocytic hypochromic anemia. The most probable diagnosis is juvenile nasopharyngeal angiofibroma . The age of the patient (12 years) , Sex: (male), and presentation (nasal bleeding) all favor it.</p>\n<p><strong>Highyeild:</strong></p><p>JUVENILE NASOPHARYNGEAL ANGIOFIBROMA It Is non – a capsulated, Benign fast growing (Aggressive) and vascular tumor of the Nasopharynx, that is only seen in Juvenile males MC site - Sphenopalatine foramen Presentation A juvenile male with severe and recurrent epistaxis (artery supplying tumorMaxillary artery/Sphenopalatine artery) Cheek swelling Signs Antral sign/Holman-Miller sign : Anterior bowing of posterior maxillary wall Hondusa sign : Increase distance between maxilla and mandible Frog face deformity : Tumor is in orbit and Ethmoid - increases distance between eye and nose and proptosis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B.As far as hematomas are concerned, swelling is generally seen. Option: C. In antrochoanal polyps, the presenting symptom is unilateral nasal obstruction and not bleeding. Option: D. Age of the patient goes against Nasopharyngeal cancer.</p>\n<p><strong>Extraedge:</strong></p><p>Angiofibroma. Section shows multiple dilated vessels surrounded by fibrous stroma. (A) H&E, X100. (B) H&E, X200.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45 years old man from north India presents with a deformity as shown in the image below. He gives a history of foul-smelling purulent nasal discharge before this nodule appeared. Which of the following organisms is responsible for his condition?", "options": [{"label": "A", "text": "Rhinosporidium Seeberi", "correct": false}, {"label": "B", "text": "Klebsiella Rhinoscleromatis", "correct": true}, {"label": "C", "text": "Klebsiella Ozaenae", "correct": false}, {"label": "D", "text": "Klebsiella Oxytoca", "correct": false}], "correct_answer": "B. Klebsiella Rhinoscleromatis", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996212436-QTDE010034IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Klebsiella Rhinoscleromatis The above image and clinical scenario are suggestive of rhinoscleroma and it is caused by Klebsiella It is a gram-negative bacillus also known as Frisch bacillus . It is endemic in north India.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOSCLEROMA It is a chronic granulomatous disease caused by Gram-negative bacillus called Klebsiella rhinoscleromatis or Frisch bacillus. STAGES Stage of Atrophy Atrophy of Nose Stage of Granuloma Formation → WOODY NOSE Stage of Cicatricial/Sclerotic → Nose is STENOSED Shape of the nose is changed → HEBRA NOSE Rhinoscleroma nose. Rhinoscleroma showing foamy Mikulicz cells (arrow) and lymphocytic infiltration (arrowheads) (H&E, x400). Mikulicz cells contain Gram-negative bacteria which can be better appreciated in sec- tions stained with Giemsa stain and examined under oil immersion lens.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Rhinosporidium seeberi causes rhinosporidiosis. Option: C. k. ozaenae causes atrophic rhinitis. Option: D. k. oxytoca is associated with hemorrhagic colitis.</p>\n<p><strong>Extraedge:</strong></p><p>GRANULOMATOUS DISEASE OF NOSE Bacterial Fungal Unspecified cause Rhinoscleroma Rhinosporidiosis Wegener's granulomatosis Syphilis Aspergillosis Nonhealing midline granuloma Tuberculosis Mucormycosis Lupus Candidiasis Sarcoidosis Rare Leprosy Histoplasmosis Blastomycosis</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 44 years old male who works in a cattle ranch presented with complaints of a mass protruding from the left nostril and blood-stained nasal discharge. The mass is pink, leafy, and polypoidal and is attached to the nasal septum. Biopsy from the lesion showed several sporangia. What is the treatment of choice for the above condition?", "options": [{"label": "A", "text": "Streptomycin and tetracycline", "correct": false}, {"label": "B", "text": "Radiotherapy", "correct": false}, {"label": "C", "text": "Excision with cauterization", "correct": true}, {"label": "D", "text": "Dapsone", "correct": false}], "correct_answer": "C. Excision with cauterization", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Excision with cauterization Surgical management ( excision of the lesion with cauterization of the base ), is always the treatment of choice in rhinosporidiosis.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOSPORIDIOSIS It is a chronic granulomatous disease caused by Rhinosporidium seeberi. CLINICAL FEATURES: Red colour polyp with dots STRAWBERRY POLYP/ MULBERRY POLYP Polyp that bleeds White dots is believed to be a fungus Treatment: Cautery Excision or Laser Excision + Dapsone (DOC) Rhinosporidiosis presenting as (A) a polypoidal mass protruding through the naris and (B) multiple sites of involvement, viz. nose, conjunctiva and tongue</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Streptomycin and tetracycline have no role in rhinosporidiosis. Option: B. Radiotherapy also has no role in rhinosporidiosis. Option: D. Dapsone is the drug of choice in rhinosporidiosis, but the treatment of choice is cautery/laser excision.</p>\n<p><strong>Extraedge:</strong></p><p>(A) Histologic section showing rhinosporidiosis (blue arrow) evoking mixed inflammatory response (H&E, x40). (B) Histologic section showing sporangium (blue arrow) which is fully packed with immature sporoblasts at the periphery and mature ones at the centre (H&E, x200).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are working as a senior resident doctor in the department of ENT. A 2-year-old child was brought with a soft, compressible swelling at the root of the nose. What is the most likely diagnosis?", "options": [{"label": "A", "text": "Meningoencephalocele", "correct": true}, {"label": "B", "text": "Dermoid cyst", "correct": false}, {"label": "C", "text": "Nasoalveolar cyst", "correct": false}, {"label": "D", "text": "Laryngeal cyst", "correct": false}], "correct_answer": "A. Meningoencephalocele", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Meningoencephalocele The above clinical scenario of an isolated solitary mass arising from the root of the nose should raise the index of suspicion of nasal meningocele or meningoencephalocele.</p>\n<p><strong>Highyeild:</strong></p><p>MENINGOENCEPHALOCELE It is the herniation of brain tissue along with its meninges through a congenital bony defect. Nasal obstruction is the most common symptom. It presents as a cystic, bluish, compressible, translucent mass at the root of the nose (nasofrontal variety), side of the nose (nasoethmoid variety), or at the anteromedial aspect of the orbit (nasoorbital variety). Investigation: CT or MRI.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Dermoid cyst occurs as a midline swelling under the skin but in front of the nasal bones. Option: C. Nasoalveolar cyst presents a smooth bulge in the lateral wall and floor of the nasal vestibule. Option: D. Laryngeal cyst arises in the aryepiglottic fold and appears as bluish, fluid-filled smooth swelling in the supraglottic larynx.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment: Transnasal endoscopic excision with the closure of the defect using bone or cartilage graft along with temporalis fascia and fat.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 34-year-old patient come to your hospital and he was diagnosed to have rhinophyma. Which of the following is true about rhinophyma:", "options": [{"label": "A", "text": "Premalignant", "correct": false}, {"label": "B", "text": "Most commonly due to diabetes mellitus", "correct": false}, {"label": "C", "text": "Hypertrophy of holocrine glands", "correct": true}, {"label": "D", "text": "More common in alcoholics", "correct": false}], "correct_answer": "C. Hypertrophy of holocrine glands", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Hypertrophy of holocrine glands Rhinophyma occurs due to hypertrophy of sebaceous glands of the tip of the Sebaceous glands are holocrine glands (i.e. the entire cell disintegrates while discharging its secretion). Rhinophyma is typically seen in middle-aged men with acne rosacea.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOPHYMA/POTATO TUMOUR Rhinophyma or potato tumour is a slow-growing benign tumour due to hypertrophy of the sebaceous glands of the tip of the nose. often seen in cases of long-standing acne rosacea. presents as a pink, lobulated mass over the nose with superficial vascular dilation. mostly affects men past middle age. Treatment consists of paring down the bulk of tumour with a sharp knife or carbon dioxide laser. Rhinophyma.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Rhinophyma is not Premalignant Option: B. No relation with diabetes mellitus. Instead, it is seen in cases of long-standing acne rosacea. Option: D. No relation with alcohol.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A histological section of a biopsy shows sporangia. Biopsy was taken from a farmer who presented with a vascular polypoidal mass protruding from his left nostril. What is the causative organism for this condition?", "options": [{"label": "A", "text": "Klebsiella Rhinoscleromatis", "correct": false}, {"label": "B", "text": "Rhinosporidium Seeberi", "correct": true}, {"label": "C", "text": "Klebsiella Ozaenae", "correct": false}, {"label": "D", "text": "Klebsiella Oxytoca", "correct": false}], "correct_answer": "B. Rhinosporidium Seeberi", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rhinosporidium Seeberi The above clinical scenario along with the histology (showing sporangia) suggestive of rhinosporidiosis caused by Rhinosporidium Seeberi.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOSPORIDIOSIS It is a chronic granulomatous disease caused by Rhinosporidium seeberi. CLINICAL FEATURES: Red color polyp with dots STRAWBERRY POLYP/ MULBERRY POLYP Polyp that bleeds White dots is believed to be a fungus Treatment: Cautery Excision or Laser Excision + Dapsone (DOC) Rhinosporidiosis presenting as (A) a polypoidal mass protruding through the naris and (B) multiple sites of involvement, viz. nose, conjunctiva and tongue</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Rhinoscleroma is caused by Klebsiella rhinoscleromatis Option: C. k. ozaenae causes atrophic rhinitis. Option: D. k. oxytoca is associated with hemorrhagic colitis.</p>\n<p><strong>Extraedge:</strong></p><p>(A) Histologic section showing rhinosporidiosis (blue arrow) evoking mixed inflammatory response (H&E, x40). (B) Histologic section showing sporangium (blue arrow) which is fully packed with immature sporoblasts at the periphery and mature ones at the centre (H&E, x200).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 33 years old patient come to your hospital and presented with atrophic dry nasal mucosa, extensive crustations, and a woody hard external nose. What is the most likely diagnosis?", "options": [{"label": "A", "text": "Atrophic Rhinitis", "correct": false}, {"label": "B", "text": "Rhinophyma", "correct": false}, {"label": "C", "text": "Rhinosporidiosis", "correct": false}, {"label": "D", "text": "Rhinoscleroma", "correct": true}], "correct_answer": "D. Rhinoscleroma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rhinoscleroma The given clinical condition is suggestive of rhinoscleroma</p>\n<p><strong>Highyeild:</strong></p><p>RHINOSCLEROMA It is a chronic granulomatous disease caused by Gram-negative bacillus called Klebsiella rhinoscleromatis or Frisch bacillus. STAGES Stage of Atrophy Atrophy of Nose Stage of Granuloma Formation → WOODY NOSE Stage of Cicatricial/Sclerotic → Nose is STENOSED Shape of the nose is changed → HEBRA NOSE Rhinoscleroma nose. Rhinoscleroma showing foamy Mikulicz cells (arrow) and lymphocytic infiltration (arrowheads) (H&E, x400). Mikulicz cells contain Gram-negative bacteria which can be better appreciated in sec- tions stained with Giemsa stain and examined under oil immersion lens.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Atrophic rhinitis is a chronic inflammation of the nose characterized by atrophy of nasal mucosa and turbinate bones. The nasal cavities are roomy and full of foul-smelling crusts. Option: B. Rhinophyma or potato tumour is a slow-growing benign tumour due to hypertrophy of the sebaceous glands of the tip of the nose Option: C. Rhinoscleroma is a chronic granulomatous disease caused by Gram-negative bacillus called Klebsiella rhinoscleromatis or Frisch bacillus.</p>\n<p><strong>Extraedge:</strong></p><p>GRANULOMATOUS DISEASE OF NOSE Bacterial Fungal Unspecified cause Rhinoscleroma Rhinosporidiosis Wegener's granulomatosis Syphilis Aspergillosis Nonhealing midline granuloma Tuberculosis Mucormycosis Lupus Candidiasis Sarcoidosis Rare Leprosy Histoplasmosis Blastomycosis</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A father brought her small child with complaints of pain and swelling in the nose and difficulty in breathing after an alleged history of falls while playing. On examination, there is bilateral swelling of the nasal septum which is tender and erythematous. Which of the following is false about the given condition?", "options": [{"label": "A", "text": "Trauma is an important cause", "correct": false}, {"label": "B", "text": "Can result in hump nose deformity", "correct": true}, {"label": "C", "text": "Can lead to abscess formation", "correct": false}, {"label": "D", "text": "Accumulation of blood under the perichondrium", "correct": false}], "correct_answer": "B. Can result in hump nose deformity", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Can result in hump nose deformity The given clinical scenario with bilateral septal swelling following trauma is suggestive of a septal hematoma and can result in saddle nose deformity , not hump nose deformity.</p>\n<p><strong>Highyeild:</strong></p><p>SEPTAL HAEMATOMA It is a collection of blood under the perichondrium or periosteum of the nasal septum. It often results from nasal trauma or septal surgery. Bilateral nasal obstruction is the commonest presenting symptom. Examination reveals smooth rounded swelling of the septum in both the nasal fossae Small hematomas can be aspirated with a wide-bore sterile needle. Larger hematomas are incised and drained by a small anteroposterior incision parallel to the nasal floor. Following drainage, the nose is packed on both sides to prevent reaccumulation. Systemic antibiotics should be given to prevent septal abscesses.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Trauma is an important cause of septal hematoma is a true statement. Option: C. Abscess formation can be a complication of a septal hematoma. Option: D. Septal hematoma is a collection of blood under the perichondrium or periosteum of the nasal septum.</p>\n<p><strong>Extraedge:</strong></p><p>Septal haematoma, if not drained, may organize into fibrous tissue leading to a permanently thickened septum. If secondary infection supervenes, it results in a septal abscess with necrosis of cartilage and depression of the nasal dorsum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The ideal treatment of rhinosporidiosis is", "options": [{"label": "A", "text": "Rifampicin", "correct": false}, {"label": "B", "text": "Excision with cautery at base", "correct": true}, {"label": "C", "text": "Dapsone", "correct": false}, {"label": "D", "text": "Antibiotics", "correct": false}], "correct_answer": "B. Excision with cautery at base", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Excision with cautery at base Rhinosporidiosis is a chronic granulomatous disease caused by Rhinosporidium seeberi . The best treatment is cautery/laser excision.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOSPORIDIOSIS It is a chronic granulomatous disease caused by Rhinosporidium seeberi . CLINICAL FEATURES: Red colour polyp with dots STRAWBERRY POLYP/ MULBERRY POLYP Polyp that bleeds White dots is believed to be a fungus Treatment: Cautery Excision or Laser Excision + Dapsone (DOC) Rhinosporidiosis presenting as (A) a polypoidal mass protruding through the naris and (B) multiple sites of involvement, viz. nose, conjunctiva and tongue</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Rifampicin has no role in the treatment of Rhinosporidiosis. Option: C. Dapsone is the drug of choice for rhinosporidiosis but not the treatment of choice. Option: D. Antibiotics also have no role in the treatment of Rhinosporidiosis.</p>\n<p><strong>Extraedge:</strong></p><p>(A) Histologic section showing rhinosporidiosis (blue arrow) evoking mixed inflammatory response (H&E, x40). (B) Histologic section showing sporangium (blue arrow) which is fully packed with immature sporoblasts at the periphery and mature ones at the centre (H&E, x200).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The deformity depicted here is", "options": [{"label": "A", "text": "Crooked Nose", "correct": false}, {"label": "B", "text": "Deviated Nose", "correct": false}, {"label": "C", "text": "Rhinophyma", "correct": false}, {"label": "D", "text": "Saddle Nose", "correct": true}], "correct_answer": "D. Saddle Nose", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996212444-QTDE010044IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Saddle Nose In the saddle nose Nasal dorsum is depressed (sagging of the bridge of the nose). Depressed nasal dorsum may involve either bony, cartilaginous, or both bony and cartilaginous components . The most common etiology is Nasal trauma.</p>\n<p><strong>Highyeild:</strong></p><p>Depressed nasal dorsum may involve bony, cartilaginous, or both bony and cartilaginous components of the nasal dorsum. Nasal trauma causing depressed fractures is the most common etiology. It can also result from excessive removal of the septum in submucous resection, destruction of septal cartilage by haematoma or abscess, sometimes by leprosy, tuberculosis or syphilis. The deformity can be corrected by augmentation rhinoplasty by filling the dorsum with cartilage, bone, or a synthetic implant.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In a crooked nose, the midline of the dorsum from the frontonasal angle to the tip is curved in a C- or S-shaped manner. Option: B. In a deviated nose, the midline is straight but deviated to one side. Option: C. Rhinophyma or potato tumour is a slow-growing benign tumour due to hypertrophy of the sebaceous glands of the tip of the nose</p>\n<p><strong>Extraedge:</strong></p><p>If depression is only cartilaginous, cartilage is taken from the nasal septum or auricle and laid in single or multiple layers. If deformity involves both cartilage and bone, cancellous bone from the iliac crest is the best. Au- tografts (taken from the same individual) are preferred to allografts (taken from other individuals or cadavers). Saddle deformity can also be corrected by synthetic implants of silicone or Teflon but they are likely to be extruded.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "False about condition seen below is:", "options": [{"label": "A", "text": "Also known as elephantiasis of nose", "correct": false}, {"label": "B", "text": "Seen in long-standing cases of acne rosacea", "correct": false}, {"label": "C", "text": "It Is Premalignant", "correct": true}, {"label": "D", "text": "Hypertrophy of sebaceous gland", "correct": false}], "correct_answer": "C. It Is Premalignant", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996212449-QTDE010045IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>It Is Premalignant Above image show rhinophyma/potato tumor. It is not a premalignant condition.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOPHYMA/POTATO TUMOUR Rhinophyma or potato tumour is a slow-growing benign tumour due to hypertrophy of the sebaceous glands of the tip of the nose. often seen in cases of long-standing acne rosacea. presents as a pink, lobulated mass over the nose with superficial vascular dilation. mostly affects men past middle age. Treatment consists of paring down the bulk of tumour with a sharp knife or carbon dioxide laser. Rhinophyma</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Rhinophyma also known as elephantiasis of the nose is a true statement. Option: B. Rhinophyma is Seen in long-standing cases of acne rosacea. Option: D. Rhinophyma or potato tumor is a slow-growing benign tumour due to hypertrophy of the sebaceous glands of the tip of the nsoe.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Rhinophyma is associated with:", "options": [{"label": "A", "text": "Hypertrophy of the sebaceous glands", "correct": true}, {"label": "B", "text": "Hypertrophy of sweat glands", "correct": false}, {"label": "C", "text": "Hyperplasia of endothelial cells", "correct": false}, {"label": "D", "text": "Hyperplasia of epithelial cells", "correct": false}], "correct_answer": "A. Hypertrophy of the sebaceous glands", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Hypertrophy of the sebaceous glands Rhinophyma or potato tumour is a slow-growing benign tumour due to hypertrophy of the sebaceous glands of the tip of the nose.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOPHYMA/POTATO TUMOUR Rhinophyma or potato tumour is a slow-growing benign tumour due to hypertrophy of the sebaceous glands of the tip of the nose. often seen in cases of long-standing acne rosacea. presents as a pink, lobulated mass over the nose with superficial vascular dilation. mostly affects men past middle age. Treatment consists of paring down the bulk of tumour with a sharp knife or carbon dioxide laser. Rhinophyma</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Hypertrophy of sweat glands is not seen in rhinophyma. Option: C. Hyperplasia of endothelial cells is also not seen in rhinophyma. Option: D. Hyperplasia of epithelial cells is also not seen in rhinophyma.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 10 year male present with h/o recurrent sinusitis with nasal polyps. He was admitted to the hospital several times for chest infections and is suffering from malabsorption syndrome. The most appropriate test for the diagnosis is", "options": [{"label": "A", "text": "Total Ige Estimation", "correct": false}, {"label": "B", "text": "ESR", "correct": false}, {"label": "C", "text": "Sweat chloride Test", "correct": true}, {"label": "D", "text": "Level of ACE", "correct": false}], "correct_answer": "C. Sweat chloride Test", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Sweat chloride Test The given history points towards the diagnosis i.e. CYSTIC FIBROSIS. The sweat chloride test is the most appropriate test for its diagnosis out of the given options.</p>\n<p><strong>Highyeild:</strong></p><p>CYSTIC FIBROSIS Recurrent sinusitis Nasal polyps are the most distinctive of physical findings in cystic fibrosis Repeated chest infections due to thick viscid mucus which cannot be expelled easily Malabsorption syndrome mainly due to deficiency of pancreatic enzymes Diagnosis- Sweat test: Increased sweat chloride level >60 mEq/L (at least 2 separate values) OR 2 CF mutations OR Single transepithelial nasal potential difference The below-given Image shows maxillary sinus with purulent discharge (marked with arrow) and a large obstructing polyp labeled P</p>\n<p><strong>Extraedge:</strong></p><p>Twenty percent of patients with cystic fibrosis form polypi. It is due to abnormal mucus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 50 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 9</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Electrocochleography & Cochlear Vs Retro-Cochlear - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 9</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 9 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "The Image demonstrate which test:", "options": [{"label": "A", "text": "Dix-Hallpike Test.", "correct": true}, {"label": "B", "text": "Romberg test", "correct": false}, {"label": "C", "text": "Fistula test", "correct": false}, {"label": "D", "text": "Epley’s maneuver", "correct": false}], "correct_answer": "A. Dix-Hallpike Test.", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003440589-QTDE044001IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Dix-Hallpike Test. The above image demonstrates the Dix Hallpike test.</p>\n<p><strong>Highyeild:</strong></p><p>Dix Hillpike Test This test is beneficial when the patient complains of vertigo in certain head positions. It also helps to differentiate a peripheral from a central lesion.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. In the Romberg test, the patient is asked to stand with feet together and arms by the side with eyes first open and then closed. With the eyes open, the patient can still compensate for the imbalance, but the vestibular system is more disadvantaged with the eyes closed. In peripheral vestibular lesions, the patient sways to the side of the In central vestibular disorder, the patient shows instability. Option C. Fistula test - This test aims to induce nystagmus by producing pressure changes in the external canal which are then transmitted to the labyrinth—stimulation of the labyrinth results in nystagmus and vertigo. The test is performed by applying intermittent pressure on the tragus or using Siegel’s speculum. Option D.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are a first-year postgraduate, and you are examining the patients in the ENT ward. Which of the following patients will have a positive Rinne's test?", "options": [{"label": "A", "text": "A child with chronic suppurative otitis media", "correct": false}, {"label": "B", "text": "Impacted wax in an adolescent", "correct": false}, {"label": "C", "text": "Presbycusis in an elderly patient", "correct": true}, {"label": "D", "text": "Otosclerosis in a pregnant woman", "correct": false}], "correct_answer": "C. Presbycusis in an elderly patient", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Presbycusis in an elderly patient Rinne's test is positive for presbycusis , a type of sensorineural deafness .</p>\n<p><strong>Highyeild:</strong></p><p>Rinne’s Test In this test, the air conduction of the ear is compared with its bone conduction. A vibrating tuning fork is placed on the patient’s mastoid, and when he stops hearing, it is brought beside the meatus. If he still hears, AC is more than BC. Alternatively, the patient is asked to compare the loudness of sound heard through air and bone conduction. Rinne's test is positive when AC is longer or louder than BC. It is seen in normal persons or those having sensorineural deafness. A negative Rinne (BC > AC) is seen in conductive deafness.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. A child with chronic suppurative otitis media shows negative Rinne’s as it is conductive deafness. Option B. Impacted wax in an adolescent also shows negative Rinne’s as it is conductive deafness. Option D. Otosclerosis in a pregnant woman is also conductive deafness, showing negative Rinne’s test.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Auditory brainstem implant is kept in:", "options": [{"label": "A", "text": "Scala Tympani", "correct": false}, {"label": "B", "text": "Scala Vestibuli", "correct": false}, {"label": "C", "text": "Scala Media", "correct": false}, {"label": "D", "text": "Lateral recess of 4th ventricle", "correct": true}], "correct_answer": "D. Lateral recess of 4th ventricle", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lateral recess of 4th ventricle The implant is made to stimulate the cochlear nuclear complex in the brainstem directly by placing the implant in the lateral recess of the fourth ventricle . Such an implant is needed when CN VIII has been severed in vestibular schwannoma surgery. In these cases, cochlear implants are of no use.</p>\n<p><strong>Highyeild:</strong></p><p>Auditory Brainstem Implant This implant is designed to stimulate the cochlear nuclear complex in the brainstem directly by placing the implant in the lateral recess of the fourth ventricle. Such an implant is needed when CN VIII has been severed in surgery for vestibular schwannoma. In these cases, cochlear implants are obviously of no use. In unilateral acoustic neuroma, ABI is not necessary as hearing is possible from the contralateral side, but in bilateral acoustic neuromas, as in NF2, rehabilitation is required by ABI.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 50-year-old male patient was diagnosed with Meniere’s disease. What kind of result will be present in electrocochleography for this patient?", "options": [{"label": "A", "text": "SP /AP = 30 %", "correct": false}, {"label": "B", "text": "SP/ AP is greater than 30 %", "correct": true}, {"label": "C", "text": "SP /AP is less than 30 %", "correct": false}, {"label": "D", "text": "SP = AP", "correct": false}], "correct_answer": "B. SP/ AP is greater than 30 %", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>SP/ AP is greater than 30 % This is the gold standard method for diagnosing Ménière’s disease ; however, it is not commonly used in clinical practice. Usually, the ratio of summating potential (SP) to action potential (AP) is 30%. In Ménière’s disease , SP/AP ratio is greater than 30%.</p>\n<p><strong>Highyeild:</strong></p><p>Electrocochleography In Meniere’s Disease Electrocochleography. (A) Normal ear. (B) Ear with Mé- nière's disease. Voltage of summating potential (SP) is compared with that of action potential (AP). Normally SP is 30% of AP. This ratio is enhanced in Ménière's disease.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 40-year-old female patient complained of unilateral sensorineural hearing loss, continuous Tinnitus on the affected side, and Sudden onset Vertigo. It spontaneously resolves and increases with head movement and exertion. On investigation, electrocochleography shows a Broadening of the AP and SP waveforms. Large cochlear microphony.Presence of action potential at stimulus intensity less than the threshold and inaudible to the patient. What would be your probable diagnosis?", "options": [{"label": "A", "text": "Acoustic Neuroma", "correct": true}, {"label": "B", "text": "Hemangioma of facial nerve", "correct": false}, {"label": "C", "text": "Aberrant internal carotid artery", "correct": false}, {"label": "D", "text": "Meniere’s Disease.", "correct": false}], "correct_answer": "A. Acoustic Neuroma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Acoustic Neuroma Unilateral sensorineural hearing loss , Tinnitus which is continuous on the affected side. Sudden onset Vertigo spontaneously resolves and increases with head movement and exertion. Cochleovestibular Symptoms are the earliest symptoms when the tumour is still intracanalicular and are caused by pressure on cochlear or vestibular nerve fibres or the internal carotid artery. Electrocochleography shows a Broadening of the AP and SP waveforms—large cochlear microphony . The presence of action potential at stimulus intensity less than the threshold and inaudible to the patient are the features of acoustic neuroma .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. Hemangioma of facial nerve arises from facial nerve or geniculate ganglion. They are extra neural and cause facial paralysis by pressure. They may cause recurrent facial weakness. Gadolinium-enhanced MRI or CT can help in diagnosis. Option C. Aberrant internal carotid artery presents as a pink, pulsatile mass in the anterior part of the middle ear. It is incidentally found on tympanotomy and diagnosed by HRCT temporal bone and MR angiography. Option D. In Meniere’s disease, vertigo is an episodic, fluctuating type of hearing loss in the same ear and has tinnitus and a sense of fullness in the ear. SP/ AP is greater than 30 % in electrocochleography.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient presented with complaints of episodic vertigo, fluctuating type of hearing loss, and tinnitus and was diagnosed with Meniere’s disease after doing an investigation with electrocochleography. The summating potential has been used in the diagnosis of Meniere’s disease. From where is summating potential produced?", "options": [{"label": "A", "text": "Nerve Fiber", "correct": false}, {"label": "B", "text": "Utricle", "correct": false}, {"label": "C", "text": "Saccule", "correct": false}, {"label": "D", "text": "Hair Cells", "correct": true}], "correct_answer": "D. Hair Cells", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Hair Cells Summating potential (SP) is a DC potential and follows the “envelope” of stimulating sound. It is produced by hair cells.</p>\n<p><strong>Highyeild:</strong></p><p>Summating Potential It is a DC potential and follows the “envelope” of stimulating sound. It is produced by hair cells. It may be negative or positive. SP has been used in the diagnosis of Ménière’s disease. It is superimposed on the VIII nerve action potential. Cochlear microphonics and Summating potential are receptor potentials in other sensory end-organs. They differ from action potentials in that: They are graded rather than all or none phenomenon Have no latency Are not propagated Have no post-response refractory period.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "65-year-old woman presented with acute bilateral hearing loss. The patient showed no neurological deficits. Her tympanic membrane was normal. A pure tone audiometry test revealed bilateral high-frequency loss and pure tone shows evidence of sensorineural hearing loss. Speech discrimination scores were very poor. Wave V was absent in the brainstem evoked response audiometry, and the stapedial reflex was absent. Where would be the site of the lesion?", "options": [{"label": "A", "text": "Tympanic Membrane", "correct": false}, {"label": "B", "text": "Cochlear Lesion", "correct": false}, {"label": "C", "text": "Retrocochlear Lesion", "correct": true}, {"label": "D", "text": "Patient is normal", "correct": false}], "correct_answer": "C. Retrocochlear Lesion", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Retrocochlear Lesion In Retrocochlear lesion – pure tone audiogram shows a sensorineural hearing loss ; speech discrimination score is very poor, roll over phenomenon is present , recruitment is absent, SISI score 0-20 %, threshold tone decay test is above 25 dB, stapedial reflex absent, abnormal stapedial reflex decay, BERA - wave V delayed or missing .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. The question itself says that the tympanic membrane is normal. Option B. In the case of cochlear lesion -pure tone audiogram shows SNHL, speech discrimination below 90%, roll over phenomenon is absent, recruitment is present, SISI score is over 70%, threshold tone decay is less than 25 dB, the stapedial reflex is present, stapedial reflex decay is normal, BERA- the normal interval between wave I and V</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 30-year-old male patient comes to the OPD complaining of bilateral hearing loss. His tympanic membrane is normal. In pure tone audiometry, air conduction and bone conduction thresholds are increased, and No air-bone gap, Bekesy audiometry is type II, and the SISI score is over 70 %. What type of deafness is this?", "options": [{"label": "A", "text": "Conductive Deafness", "correct": false}, {"label": "B", "text": "Cochlear Deafness", "correct": true}, {"label": "C", "text": "Retrocochlear Deafness", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "B. Cochlear Deafness", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cochlear Deafness In the sensorineural hearing loss, pure tone audiogram shows no air-bone gap , speech discrimination below 90% , roll over phenomenon is absent, recruitment is present, SISI score is over 70%, t hreshold tone decay is less than 25 dB , the stapedial reflex is present, stapedial reflex decay is normal, BERA- the normal interval between wave I and V. Bakesy audiometry is type II in cochlear deafness. So the correct answer is option B.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option A. In conductive deafness -Negative Rinne test, i.e. BC > AC, Weber lateralised to poorer ear, Normal absolute bone conduction, Low frequencies affected more, Audiometry shows bone conduction better than air conduction with an air-bone gap. The more significant the air-bone gap, the more the conductive loss, Loss is not more than 60 dB, and Speech discrimination is good. Option C. Retrocochlear lesion – pure tone audiogram shows a sensorineural hearing loss; speech discrimination score is very poor, roll over phenomenon is present, recruitment is absent , SISI score 0-20 %, threshold tone decay test is above 25 dB, stapedial reflex absent, abnormal stapedial reflex decay, BERA- wave V delayed or missing.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 50-year-old male patient presented with vertigo, tinnitus, and hearing loss. As a part of the investigation, the doctor has done non-invasive techniques like auditory brainstem response for diagnosing brain stem pathology. Wave II is obtained in this patient. Where is the exact anatomic site of the neural generators of this wave?", "options": [{"label": "A", "text": "Distal part of CN VIII", "correct": false}, {"label": "B", "text": "Proximal part of CN VIII near the brain stem", "correct": true}, {"label": "C", "text": "Cochlear Nucleus", "correct": false}, {"label": "D", "text": "Superior olivary complex", "correct": false}], "correct_answer": "B. Proximal part of CN VIII near the brain stem", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Proximal part of CN VIII near the brain stem Wave II is due to the proximal part of CN VIII near the brain stem.</p>\n<p><strong>Highyeild:</strong></p><p>Auditory Brainstem Response/Brainstem Evoked Response Audiometry Wave I Distal part of CN VIII Wave II Proximal part of CN VIII near the brainstem Wave III Cochlear nucleus Wave IV Superior olivary complex Wave V Lateral lemniscus Waves VI and VII Inferior colliculus Brainstem auditory evoked potentials.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Wave I is due to the distal part of cranial nerve VIII. Option C. Wave III is due to the cochlear nucleus. Option D. Wave IV is due to the superior olivary nucleus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 19 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 6</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Embryology of Ear - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 6</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 6 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A structure in the ear is considered to be developed from the invagination of the first cleft and first pouch and it is considered to be developed from all three germ layers. Identify the correct structure.", "options": [{"label": "A", "text": "Tympanic Membrane", "correct": true}, {"label": "B", "text": "Pinna", "correct": false}, {"label": "C", "text": "Ear Ossicles", "correct": false}, {"label": "D", "text": "External auditory meatus", "correct": false}], "correct_answer": "A. Tympanic Membrane", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tympanic Membrane Tympanic membrane develops from all three germinal layers: Middle fibrous layer by the mesoderm The outer epithelial layer is formed by the ectoderm Inner mucosal layer by the endoderm</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Pinna develops from six hillocks of His. Option: C. Malleus and incus are derived from the mesoderm of the first arch while the stapes develop from the second arch except its footplate and annular ligament which are derived from the otic capsule. Option: D. External auditory meatus develops from the first branchial cleft .</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An ENT surgeon is performing a mastoidectomy and has difficulty in accessing the mastoid antrum due to the presence of a bony septum. Persistence of which of the following structures is responsible?", "options": [{"label": "A", "text": "Frontozygomatic Suture", "correct": false}, {"label": "B", "text": "Petrosquamous Suture", "correct": true}, {"label": "C", "text": "Temporosquamous Suture", "correct": false}, {"label": "D", "text": "Petromastoid Suture", "correct": false}], "correct_answer": "B. Petrosquamous Suture", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Petrosquamous Suture The persistence of petrosquamous suture is called Korner's septum and it is responsible for the difficulty in locating the antrum and the deeper cells.</p>\n<p><strong>Highyeild:</strong></p><p>KORNER SEPTUM The persistence of petrosquamous suture is called Korner's septum and it is responsible for the difficulty in locating the antrum and the deeper cells. Korner's septum separates superficial squamosal cells from the deep petrosal cells. Incomplete removal of disease at mastoidectomy can occur if Korner's septum is not identified. Korner's septum (A) as seen on mastoid exploration, (B) in the coronal section of the mastoid; in its presence, there is difficulty in locating the antrum which lies deep to it.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Eustachian tube develops from:", "options": [{"label": "A", "text": "2nd and 3rd pharyngeal pouch", "correct": false}, {"label": "B", "text": "1st Pharyngeal Pouch", "correct": true}, {"label": "C", "text": "4th Pharyngeal Pouch", "correct": false}, {"label": "D", "text": "3rd Pharyngeal Pouch", "correct": false}], "correct_answer": "B. 1st Pharyngeal Pouch", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>1st Pharyngeal Pouch The Eustachian tube , tympanic cavity, attic, antrum, and mastoid develops from the endoderm of tubotympanic recess which arises from the first and partly from the second pharyngeal pouch .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Palatine tonsil develops from the 2nd pharyngeal pouch and the thymus develops from the 3rd pharyngeal pouch. Option: C. Superior parathyroid gland develops from the 4th pharyngeal pouch. Option: D. Inferior parathyroid gland develops from the 3rd pharyngeal pouch.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All of the following are of the size of adults at birth except?", "options": [{"label": "A", "text": "Tympanic Membrane", "correct": false}, {"label": "B", "text": "Ossicle", "correct": false}, {"label": "C", "text": "Tympanic Annulus", "correct": false}, {"label": "D", "text": "Mastoid Antrum", "correct": true}], "correct_answer": "D. Mastoid Antrum", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mastoid Antrum Development of the mastoid air cell system does not occur until after birth, with about 90% of air cell formation being completed by the age of six with the remaining 10% taking place up to the age of 18. Hence, mastoid antrum which is not complete without its air cells, development is not complete at birth.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "True regarding \"Preauricular sinus\" is:", "options": [{"label": "A", "text": "Failure of fusion of 1st and 2nd arch", "correct": true}, {"label": "B", "text": "Commonly seen at the tragus", "correct": false}, {"label": "C", "text": "Treatment is an observation", "correct": false}, {"label": "D", "text": "All of the Above", "correct": false}], "correct_answer": "A. Failure of fusion of 1st and 2nd arch", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Failure of fusion of 1st and 2nd arch. Failure of fusion of the 1st and 2nd arch leads to the formation of the preauricular sinus.</p>\n<p><strong>Highyeild:</strong></p><p>PREAURICULAR SINUS Failure of fusion of the 1st and 2nd arch leads to the formation of the preauricular sinus. commonly seen at the root of helix blind track lined by squamous epithelium It may get repeatedly infected causing purulent discharge Infected preauricular sinus with pus exuding from the opening.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Preauricular sinus is commonly seen at the root of the helix. Option: C. Treatment is surgical excision of the track if the sinus gets repeatedly infected.</p>\n<p><strong>Extraedge:</strong></p><p>Abscess may also form Treatment is surgical excision of the track if the sinus gets repeatedly infected.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The nerve of the pterygoid canal is also known as:", "options": [{"label": "A", "text": "Arnold's Nerve", "correct": false}, {"label": "B", "text": "Vidian Nerve", "correct": true}, {"label": "C", "text": "Nerve of Kuntz", "correct": false}, {"label": "D", "text": "None of the above.", "correct": false}], "correct_answer": "B. Vidian Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Vidian Nerve A greater superficial petrosal nerve joins the deep petrosal nerve to form the nerve of the pterygoid canal or also called VIDIAN NERVE.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Auricular branch of the vagus (CN X), also called Arnold’s nerve, supplies the concha and corresponding eminence on the medial surface. Option: C. Tympanic branch of CN IX is called Jacobson's nerve.</p>\n<p><strong>Extraedge:</strong></p><p>The vidian nerve reaches the pterygopalatine ganglion to supply the lacrimal gland and mucous glands of the nose, palate, and pharynx.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 16 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 13</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Embryology of Nose & Sinuses - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 13</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 13 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 27 years old boxers suffered a blow to the nose. A resident doctor suspects a fracture of his external nasal bones. Which of the following will you not expect to contribute to the fracture?", "options": [{"label": "A", "text": "Nasal Bone", "correct": false}, {"label": "B", "text": "Frontal Bone", "correct": false}, {"label": "C", "text": "Sphenoid Bone", "correct": true}, {"label": "D", "text": "Maxilla", "correct": false}], "correct_answer": "C. Sphenoid Bone", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Sphenoid Bone Sphenoid bone does not contribute to the bony part of the external nose.</p>\n<p><strong>Highyeild:</strong></p><p>BONY PART OF THE EXTERNAL NOSE The upper one-third of the external nose is bony while the lower two-thirds are cartilaginous. The bony part consists of Two nasal bones meet in the midline. The upper part of the nasal process of the frontal bones. Frontal processes of the maxillae.</p>\n<p><strong>Extraedge:</strong></p><p>Cartilaginous Part of the external nose Upper lateral cartilage. Lower lateral cartilages (alar cartilages). Lesser alar (or sesamoid) cartilage. Septal cartilage.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 34-year-old male comes with complaints of bleeding from his Nose and mouth. On examination with a posterior rhinoscopy mirror, an irregular mass is seen at the choana. What are the choanae also known as?", "options": [{"label": "A", "text": "Anterior Nares", "correct": false}, {"label": "B", "text": "Posterior Nares", "correct": true}, {"label": "C", "text": "Limen Nasi", "correct": false}, {"label": "D", "text": "Agger Nasi", "correct": false}], "correct_answer": "B. Posterior Nares", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Posterior Nares The choanae are also known as posterior nares.</p>\n<p><strong>Highyeild:</strong></p><p>Each nasal cavity communicates with the exterior through the anterior nares or nostril and with the nasopharynx through the posterior nares , posterior nasal aperture, or the choanae.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Each nasal cavity communicates with the exterior through the anterior nares or nostrils. Option: C. Limen nasi or the nasal valve is the upper limit of the nasal vestibule on the lateral wall. It demarcates the skin-lined vestibule with the mucosa-lined nasal cavity properly. Option: D. Agger nasi air cells are the most anterior ethmoidal air cells lying anterolateral and inferior to the frontal recess and anterior and above the attachment of the middle turbinate.</p>\n<p><strong>Extraedge:</strong></p><p>The skin over the nasal bones and upper lateral cartilages is thin and freely mobile while that covering the alar cartilages is thick and adherent, and contains many sebaceous glands. It is the hypertrophy of these sebaceous glands which gives rise to a lobulated tumour called rhinophyma.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In a 24 year old patient with refractory posterior epistaxis, a transnasal endoscopic sphenopalatine artery ligation is planned. During the operation, the surgeon asks you to clip the artery at the sphenopalatine foramen. Where will you locate the site of ligation?", "options": [{"label": "A", "text": "Anterior end of the middle turbinate", "correct": false}, {"label": "B", "text": "Posterior end of the middle turbinate", "correct": true}, {"label": "C", "text": "Anterior end of superior turbinate", "correct": false}, {"label": "D", "text": "Posterior end of superior turbinate", "correct": false}], "correct_answer": "B. Posterior end of the middle turbinate", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Posterior end of the middle turbinate Transnasal endoscopic sphenopalatine artery ligation (TESPAL) is done to control refractory posterior epistaxis by ligating the sphenopalatine artery . It emerges from the sphenopalatine foramen which is situated at the posterior end of the middle turbinate.</p>\n<p><strong>Highyeild:</strong></p><p>Branches of sphenopalatine ganglion also emerge from the sphenopalatine foramen. It supplies most of the posterior 2/3rd of the nasal cavity. It can be anesthetized by placing a pledget of cotton soaked in an anesthetic solution at the foramen.</p>\n<p><strong>Extraedge:</strong></p><p>The sphenopalatine foramen is also the most common site for the origin of angiofibroma.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A young patient presents with nasal congestion and the Cottles test is performed. Which of the following does not contribute to the boundaries of the involved structure?", "options": [{"label": "A", "text": "Upper lateral cartilage", "correct": false}, {"label": "B", "text": "Inferior Turbinate", "correct": false}, {"label": "C", "text": "Middle Turbinate", "correct": true}, {"label": "D", "text": "Septum", "correct": false}], "correct_answer": "C. Middle Turbinate", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Middle Turbinate In Cottle’s test structure involved is the nasal valve. The middle turbinate does not contribute to the boundaries of the nasal valve.</p>\n<p><strong>Highyeild:</strong></p><p>BOUNDARIES OF NASAL VALVE Laterally - lower border of upper lateral cartilage, fibrofatty tissue, and anterior end of the inferior turbinate. Medially - cartilaginous nasal septum. Caudally - floor of pyriform aperture. In Cottle's test, the cheek of the patient is drawn laterally during quiet breathing. If this improves the airway on the test side, the test is considered to be positive. This suggests an abnormality of the vestibular component of the nasal valve causing nasal obstruction. Cottle test: On pulling the cheek away from the mid- line, the nasal valve opens, increasing the airflow from that side of the nasal cavity.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Upper lateral cartilage forms the lateral boundary of the nasal valve. Option: B. Inferior turbinate also forms the lateral boundary of the nasal valve. Option: D. Cartilagenous nasal septum forms the medial boundary of the nasal valve.</p>\n<p><strong>Extraedge:</strong></p><p>In Cottle's test, the cheek of the patient is drawn laterally during quiet breathing. If this improves the airway on the test side, the test is considered to be positive. This suggests an abnormality of the vestibular component of the nasal valve causing nasal obstruction.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A father brings his 1-year-old baby to the pediatrician after dropping his baby when shifting from one arm to another. The pediatrician orders a CT head, which is normal. Which of the following structures would you not expect to find in the CT?", "options": [{"label": "A", "text": "Maxillary Sinus", "correct": false}, {"label": "B", "text": "Frontal Sinus", "correct": true}, {"label": "C", "text": "Ethmoid Sinus", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "B. Frontal Sinus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Frontal Sinus The frontal sinus is absent or rudimentary at birth . Radiologically , the frontal sinus is able to be identified at 6 years and reaches full size only after puberty.</p>\n<p><strong>Highyeild:</strong></p><p>FRONTAL SINUS Each frontal sinus is situated between the inner and outer tables of the frontal bone, above and deep to the supraorbital margin. It varies in shape and size and is often loculated. The loculations are also called scallops. The two frontal sinuses are often asymmetric and the intervening bony septum is thin and often obliquely placed or may even be deficient. The frontal sinus may be absent on one or both sides or it may be very large extending into the orbital plate in the roof of the orbit.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Maxillary sinus is present at birth and can be radiologically identified 4-5 months after birth. Option: C. Ethmoidal sinus is also present at birth and can be radiologically identified by 1 year of age.</p>\n<p><strong>Extraedge:</strong></p><p>The anterior wall of the sinus is related to the skin over the forehead; the inferior wall, to the orbit and its contents; and the posterior wall to the meninges and frontal lobe of the brain. The opening of the frontal sinus is situated on its floor and leads into the middle meatus directly or through a canal called the frontonasal duct.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Most of the facial sinuses are opened into the middle meatus. Which of the following sinuses does not open into the middle meatus?", "options": [{"label": "A", "text": "Frontal Sinus", "correct": false}, {"label": "B", "text": "Maxillary Sinus", "correct": false}, {"label": "C", "text": "Anterior ethmoidal sinus", "correct": false}, {"label": "D", "text": "Posterior Ethmoidal Sinus", "correct": true}], "correct_answer": "D. Posterior Ethmoidal Sinus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Posterior Ethmoidal Sinus The posterior ethmoidal sinus does not open into the middle meatus . It opens into the superior meatus. Frontal, maxillary, and anterior ethmoidal sinuses open into the middle meatus.</p>\n<p><strong>Highyeild:</strong></p><p>OPENINGS OF SINUS IN LATERAL WALL OF NOSE</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Frontal sinus has an opening in the middle meatus. Option B. Maxillary sinus also has an opening in the middle meatus. Option C. Anterior ethmoidal sinus also has an opening in the middle meatus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A pathologist is examining the specimen of a maxillectomy. Which of the following epithelium do you expect to find lining the maxillary sinus?", "options": [{"label": "A", "text": "Keratinising stratified squamous epithelium", "correct": false}, {"label": "B", "text": "Ciliated columnar epithelium", "correct": true}, {"label": "C", "text": "Nonkeratinizing stratified squamous epithelium", "correct": false}, {"label": "D", "text": "Simple columnar epithelium", "correct": false}], "correct_answer": "B. Ciliated columnar epithelium", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ciliated columnar epithelium Paranasal sinuses are lined by ciliated columnar epithelium with goblet cells that secrete mucus. They're lined by the mucous membrane , which is continuous with that of the nasal cavity through the ostia of the sinuses.</p>\n<p><strong>Highyeild:</strong></p><p>PARANASAL SINUS Paranasal sinuses are air-containing cavities in certain bones of the skull. They are four on each side. Clinically, paranasal sinuses have been divided into two groups. Anterior group Posterior group Maxillary Frontal Anterior ethmoidal sinus Posterior ethmoidal sinus (opens in superior meatus)</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A child presents to a resident doctor in the department of ENT with congenital dacryocystitis and requires probing of the nasolacrimal duct to relieve the obstruction. Where will the probe emerge in the nasal cavity?", "options": [{"label": "A", "text": "Superior Meatus", "correct": false}, {"label": "B", "text": "Middle Meatus", "correct": false}, {"label": "C", "text": "Inferior Meatus", "correct": true}, {"label": "D", "text": "Sphenoethmoidal Recess", "correct": false}], "correct_answer": "C. Inferior Meatus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Inferior Meatus The probe emerges in the inferior meatus because the opening of the nasolacrimal duct is present in the inferior meatus.</p>\n<p><strong>Highyeild:</strong></p><p>The nasolacrimal duct runs downwards, backward, and laterally from the lacrimal sac and opens into the inferior meatus. It is bounded by a mucosal valve called the valve of Hasner in the inferior meatus.</p>\n<p><strong>Extraedge:</strong></p><p>In congenital dacryocystitis, probing of the nasolacrimal duct is done at 9-12 months of age if symptoms persist past 6 months of age and aren't relieved by local massage and topical antibiotics.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A resident doctor while performing functional endoscopic sinus surgery, the surgeon points out the Haller cells and their clinical significance. Which of the following is correct about these cells?", "options": [{"label": "A", "text": "They are related to the maxillary sinus", "correct": true}, {"label": "B", "text": "They are related to the optic nerve", "correct": false}, {"label": "C", "text": "They are related to the sphenoid sinus", "correct": false}, {"label": "D", "text": "They are related to the facial nerve", "correct": false}], "correct_answer": "A. They are related to the maxillary sinus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>They are related to the maxillary sinus Haller cells (infraorbital ethmoidal air cells) are a type of ethmoidal cells that are situated in the roof of the maxillary sinus . Sometimes they may enlarge and block the opening of the maxillary sinus leading to maxillary sinusitis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 44 years old male comes after a road traffic accident with a fracture of the nasal septum. Which of the following structures does not contribute to the formation of the nasal septum?", "options": [{"label": "A", "text": "Septal Cartilage", "correct": false}, {"label": "B", "text": "Vomer", "correct": false}, {"label": "C", "text": "Perpendicular plate of the ethmoid", "correct": false}, {"label": "D", "text": "Perpendicular plate of the sphenoid", "correct": true}], "correct_answer": "D. Perpendicular plate of the sphenoid", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Perpendicular plate of the sphenoid The perpendicular plate of the sphenoid does not contribute to the formation of the nasal septum .</p>\n<p><strong>Highyeild:</strong></p><p>Septum proper is an osseocartilaginous framework made up of: Septal or quadrilateral cartilage Perpendicular plate of the ethmoid Vomer</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Septal or quadrilateral cartilage contributes to the formation of the septum proper. Option: B. Perpendicular plate of ethmoid also contributes to the formation of septum proper. Option: C. Vomer also contributes to the formation of the septum proper.</p>\n<p><strong>Extraedge:</strong></p><p>Other bones which make minor contributions are - the crest of nasal bones, the nasal spine of the frontal bone, the rostrum of the sphenoid, the crest of palatine bones and the crest maxilla, and the anterior nasal spine of the maxilla.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are working as a senior resident doctor in the department of ENT while performing diagnostic nasal endoscopy, which of the following structures would you not see during the third pass?", "options": [{"label": "A", "text": "Agger Nasi", "correct": false}, {"label": "B", "text": "Atrium of the middle meatus", "correct": false}, {"label": "C", "text": "Fronto-Nasal Duct", "correct": false}, {"label": "D", "text": "Nasolacrimal Duct", "correct": true}], "correct_answer": "D. Nasolacrimal Duct", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Nasolacrimal Duct The third pass in diagnostic nasal endoscopy involves the examination of the middle meatus in detail. The nasolacrimal duct opens into the inferior meatus , hence it will not be visualized here.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A . Agger nasi is an elevation just anterior to the attachment of the middle turbinate. When pneumatised and enlarged it encroaches on the frontal recess area and causes mechanical obstruction to drainage of the frontal sinus leading to sinusitis. Option: B . Atrium of the middle meatus is a shallow depression lying in front of the middle turbinate and above the nasal vestibule. Option: C . Fronto-nasal duct/frontal recess is the opening of the frontal sinus into the infundibulum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 42-year-old male patient come with complain of fullness in the nose. On examination, a mass is seen in the nasal cavity. A CT scan shows irregular mass is present in the hiatus semilunaris. Where is hiatus semilunaris present?", "options": [{"label": "A", "text": "Superior Meatus", "correct": false}, {"label": "B", "text": "Middle Meatus", "correct": true}, {"label": "C", "text": "Inferior Meatus", "correct": false}, {"label": "D", "text": "Sphenoethmoidal Recess", "correct": false}], "correct_answer": "B. Middle Meatus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Middle Meatus Hiatus semilunaris is present in the middle meatus. It is a two-dimensional space bounded by bulla ethmoidalis above and uncinate process below.</p>\n<p><strong>Highyeild:</strong></p><p>LATERAL WALL OF THE NOSE</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Posterior ethmoidal sinus opens in superior meatus. Option: C. Nasolacrimal duct opens in inferior meatus. Option: D. Sphenoid sinus opens in spehnoethmoidal recess.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are performing functional endoscopic sinus surgery. Optic nerve injury following sinus surgery (FESS) is due to the removal of which of the following ethmoidal cells?", "options": [{"label": "A", "text": "Haller", "correct": false}, {"label": "B", "text": "Agger Nasi", "correct": false}, {"label": "C", "text": "Onodi Cells", "correct": true}, {"label": "D", "text": "Bullae ethmoidalis", "correct": false}], "correct_answer": "C. Onodi Cells", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Onodi Cells The onodi cell is the posterior ethmoidal cell , related superiorly or laterally to the sphenoid sinus. The optic nerve lies in its lateral wall and can be injured while operating in the cells.</p>\n<p><strong>Highyeild:</strong></p><p>ONODI CELL The sphenoethmoid cell is also called the Onodi cell. It is the most posterior cell of this group and extends along the lamina papyracea, lateral or superior to the sphenoid, and may extend 1.5 cm behind the anterior face of the sphenoid. The optic nerve and sometimes the carotid artery are related to it laterally and are in danger during endoscopic surgery.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Haller cells are a type of ethmoidal cells that are situated on the roof of the maxillary sinus. Sometimes they may enlarge and block the opening of the maxillary sinus leading to maxillary sinusitis. Option: B . Agger nasi is an elevation just anterior to the attachment of middle turbinates. Option: D. Bulla ethmoidalis is the ethmoidal cell situated behind the uncinate process and forms the posterior boundary of hiatus semilunaris through.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 23 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 9</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Epiglottitis & Croup - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 9</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 9 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 7-year-old male was rushed to the casualty by his parents due to sudden breathing difficulty. He had mild colds earlier that day but no cough or fever. He was given no medications and had no known allergies, although his younger brother was recently diagnosed with a severe peanut allergy. Temperature is 39.4 C, blood pressure is 100/65 mm Hg, pulse is 130/min, and respirations are 46/min. His oxygen saturation is 92% on room air. The patient appears anxious, drooling with inspiratory stridor, and has a hot potato-muffled voice. He seems most comfortable when sitting upright with his neck extended. Which of the following is the most likely diagnosis in this patient?", "options": [{"label": "A", "text": "Laryngotracheitis", "correct": false}, {"label": "B", "text": "Bronchiolitis", "correct": false}, {"label": "C", "text": "Diphtheria", "correct": false}, {"label": "D", "text": "Epiglottitis", "correct": true}], "correct_answer": "D. Epiglottitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Epiglottitis The above history and examination depict that child is suffering from acute epiglottitis.</p>\n<p><strong>Highyeild:</strong></p><p>Acute Epiglottitis Presents with acute onset of fever with dysphagia, drooling, and respiratory distress. Epiglottitis is an acute inflammation in the supraglottic region of the oropharynx, with inflammation of the epiglottis, vallecula, arytenoids, and aryepiglottic folds (see the image below. Signs of impending airway obstruction include restlessness, anxiety, worsening stridor, and a muffled \"hot potato\" voice. Symptoms often develop over several hours without a significant prodrome (e.g., cough, congestion, rhinorrhea). Patients may hyperextend the neck and maintain a tripod position to maximise airway diameter when significant airway swelling is present. Although epiglottitis has been drastically reduced due to widespread Haemophilus influenzae type b (Hib) vaccination, Hib remains the most common cause, even in immunised children. THUMB sign on X-ray.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Laryngotracheitis (croup) presents with a \"barky\" cough, stridor, and fever. Drooling is uncommon, and patients are typically less ill-appearing than those with epiglottitis. Option B. Bronchiolitis presents in children age < 2 with fever, cough, retractions, and crackles/wheezing. These patients do not have stridor, as bronchiolitis results from a lower respiratory tract viral infection. Option C. Diphtheria presents with a gradual onset of sore throat, low-grade fever, and a laryngeal pseudomembrane that can lead to severe respiratory distress and stridor. These patients may also have significant neck edema.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 29-year-old G2P1 female presented with bleeding and lower abdominal pain for 3 hours, giving birth to a 2.7 kg baby girl via an uneventful spontaneous vaginal delivery. However, later you were informed that the elder child of this woman has a history of cleft lip and palate. What is true about the larynx in this neonate?", "options": [{"label": "A", "text": "Epiglottis is large and omega-shaped", "correct": false}, {"label": "B", "text": "Cricoid Narrowest Part", "correct": false}, {"label": "C", "text": "It extends till C4,5,6 vertebrae", "correct": false}, {"label": "D", "text": "Both A and B", "correct": true}], "correct_answer": "D. Both A and B", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Both A and B The infant larynx is large and omega-shaped. And narrowest part of the infantile larynx is at the junction of the subglottic larynx with the trachea because cricoid cartilage is very small.</p>\n<p><strong>Highyeild:</strong></p><p>Infant's Larynx Differs from Adult in: It is high up (C2 - C4) (in adults = C3 - C6). Of equal size in both sexes (in adults, it is larger in males). Larynx is funnel-shaped. The narrowest part of the infantile larynx is at the junction of the subglottic larynx with the trachea because the cricoid cartilage is very small. Cartilages: Epiglottis is omega-shaped, soft, large and patulous. Laryngeal cartilages are soft and collapse easily The thyroid cartilage is flat Arytenoid cartilage is relatively large The cricothyroid and thyrohyoid spaces are narrow The submucosal tissue is thick and loose and becomes oedematous in response to inflammation. Vocal cords are angled and lie at a level of C8 Trachea bifurcates at level of T9</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option C. Larynx in the infant is situated high up (C2 - C4), not from C3- C6.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 5-year-old boy is brought to the emergency department by his parents due to sudden onset difficulty breathing. The child developed a mild cough and sore throat yesterday; his parents gave him acetaminophen, which improved his throat pain. However, he developed difficulty breathing this morning and was sitting in bed and leaning forward. The child appears anxious, drooling and has inspiratory stridor. On examination, the uvula is in the midline, and there is no oropharyngeal or tonsillar erythema. Lateral soft tissue X-ray of the neck shows the following findings. Which of the following is the most likely diagnosis?", "options": [{"label": "A", "text": "Croup", "correct": false}, {"label": "B", "text": "Diphtheria", "correct": false}, {"label": "C", "text": "Epiglottitis", "correct": true}, {"label": "D", "text": "Bronchiolitis", "correct": false}], "correct_answer": "C. Epiglottitis", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004519493-QTDE052003IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Epiglottitis The sign present in the image is known as the thumb sign. This sign is present in acute epiglottitis.</p>\n<p><strong>Highyeild:</strong></p><p>Acute Epiglottitis Presents with acute onset of fever with dysphagia, drooling, and respiratory distress. Epiglottitis is an acute inflammation in the supraglottic region of the oropharynx, with inflammation of the epiglottis, vallecula, arytenoids, and aryepiglottic folds (see the image below. Signs of impending airway obstruction include restlessness, anxiety, worsening stridor, and a muffled \"hot potato\" voice. Symptoms often develop over several hours without a significant prodrome (e.g., cough, congestion, rhinorrhea). Patients may hyperextend the neck and maintain a tripod position to maximise airway diameter when significant airway swelling is present. Although epiglottitis has been drastically reduced due to widespread Haemophilus influenzae type b (Hib) vaccination, Hib remains the most common cause, even in immunised children. THUMB sign on X-ray.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Laryngotracheitis (croup) presents with a \"barky\" cough, stridor, and fever. Drooling is uncommon, and patients are typically less ill-appearing than those with epiglottitis. O ption B. Diphtheria presents with a gradual onset of sore throat, low-grade fever, and a laryngeal pseudomembrane that can lead to severe respiratory distress and stridor. These patients may also have significant neck edema. Option D. Bronchiolitis presents in children age < 2 with fever, cough, retractions, and crackles/wheezing. These patients do not have stridor, as bronchiolitis results from a lower respiratory tract viral infection.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "MC cause of intermittent stridor in a 10-day-old child shortly after birth is:", "options": [{"label": "A", "text": "Laryngomalacia", "correct": true}, {"label": "B", "text": "Foreign Body", "correct": false}, {"label": "C", "text": "Vocal Nodule", "correct": false}, {"label": "D", "text": "Hypertrophy of turbinate", "correct": false}], "correct_answer": "A. Laryngomalacia", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Laryngomalacia Laryngomalacia is the most common cause of inspiratory stridor in neonates. In the case of laryngomalacia, the stridor is not constantly present; instead, it is intermittent . So laryngomalacia is also the M/C cause of intermittent stridor in neonates .</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 4-year-old child presented with fever, difficulty breathing, and irritability. Diagnosis of acute epiglottitis was made. The most common cause of death is due to which of the following reason?", "options": [{"label": "A", "text": "Acidosis", "correct": false}, {"label": "B", "text": "Atelectasis", "correct": false}, {"label": "C", "text": "Laryngospasm", "correct": false}, {"label": "D", "text": "Respiratory Obstruction", "correct": true}], "correct_answer": "D. Respiratory Obstruction", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Respiratory Obstruction Usually, respiratory obstruction is the cause of death in acute epiglottitis. Hence tracheostomy should be performed if respiratory distress is severe or occurs despite the medical management.</p>\n<p><strong>Highyeild:</strong></p><p>Acute Epiglottitis Presents with acute onset of fever with dysphagia, drooling, and respiratory distress. Epiglottitis is an acute inflammation in the supraglottic region of the oropharynx, with inflammation of the epiglottis, vallecula, arytenoids, and aryepiglottic folds (see the image below. Signs of impending airway obstruction include restlessness, anxiety, worsening stridor, and a muffled \"hot potato\" voice. Symptoms often develop over several hours without a significant prodrome (e.g., cough, congestion, rhinorrhea). Patients may hyperextend the neck and maintain a tripod position to maximise airway diameter when significant airway swelling is present. Although epiglottitis has been drastically reduced due to widespread Haemophilus influenzae type b (Hib) vaccination, Hib remains the most common cause, even in immunised children. THUMB sign on X-ray.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A child presents with stridor, barking cough and difficulty breathing for 2-3 days. He has a fever and elevated leukocyte count. All of the following statements about his condition are true, except:", "options": [{"label": "A", "text": "Steeple sign is seen on the X-ray", "correct": false}, {"label": "B", "text": "Boys are more commonly affected than girls", "correct": false}, {"label": "C", "text": "Symptoms are predominantly caused by involvement of the subglottis", "correct": false}, {"label": "D", "text": "Antibiotics form the mainstay of treatment", "correct": true}], "correct_answer": "D. Antibiotics form the mainstay of treatment", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Antibiotics form the mainstay of treatment This is a case of acute laryngeo tracheo bronchitis. The mainstay of treatment here is Humidified O2 and steroids.</p>\n<p><strong>Highyeild:</strong></p><p>Acute Laryngotracheobronchitis/Croup It is a viral infection (parainfluenza type I and II) affecting children between 6 months and three years of age. Male children are more often affected. The disease starts as an upper respiratory infection with hoarseness and croupy cough. This may be followed by difficulty breathing and an inspiratory type of stridor. A respiratory problem may gradually increase with signs of upper airway obstruction, i.e. suprasternal and intercostal recession. Steeple sign on X-ray.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Steeple sign seen on the X-ray is a true statement. O ption B. Boys are more commonly affected than girls is also true. Option C. Symptoms are predominantly caused by involvement of the subglottis is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The characteristic narrowing occurs in which part of CROUP:", "options": [{"label": "A", "text": "Trachea", "correct": false}, {"label": "B", "text": "Subglottis", "correct": true}, {"label": "C", "text": "Glottis", "correct": false}, {"label": "D", "text": "Bronchus", "correct": false}], "correct_answer": "B. Subglottis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Subglottis In Croup, the loose areolar tissue in the Subglottic region swells up and causes respiratory obstruction and stridor.</p>\n<p><strong>Highyeild:</strong></p><p>Acute Laryngotracheobronchitis/Croup It is a viral infection (parainfluenza type I and II) affecting children between 6 months and three years of age. Male children are more often affected. The disease starts as an upper respiratory infection with hoarseness and croupy cough. This may be followed by difficulty breathing and an inspiratory type of stridor. The respiratory problem may gradually increase with signs of upper airway obstruction, i.e. suprasternal and intercostal recession. Steeple sign on X-ray.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Identify the disorder seen in the X-ray below.", "options": [{"label": "A", "text": "Laryngomalacia", "correct": false}, {"label": "B", "text": "Recurrent Laryngeal Papillomatosis", "correct": false}, {"label": "C", "text": "Epiglottitis", "correct": false}, {"label": "D", "text": "Croup", "correct": true}], "correct_answer": "D. Croup", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004519928-QTDE052009IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Croup Given X-ray shows a steeple sign , which is seen in croup/acute laryngotracheobronchitis.</p>\n<p><strong>Highyeild:</strong></p><p>Acute Laryngotracheobronchitis/Croup It is a viral infection (parainfluenza type I and II) affecting children between 6 months and three years of age. Male children are more often affected. The disease starts as an upper respiratory infection with hoarseness and croupy cough. This may be followed by difficulty breathing and an inspiratory type of stridor. A respiratory problem may gradually increase with signs of upper airway obstruction, i.e. suprasternal and intercostal recession. Steeple sign on X-ray.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Identify the Cartilage marked by the arrow in the Image.", "options": [{"label": "A", "text": "Arytenoid", "correct": false}, {"label": "B", "text": "Cuneiform", "correct": false}, {"label": "C", "text": "Thyroid", "correct": false}, {"label": "D", "text": "Cricoid", "correct": true}], "correct_answer": "D. Cricoid", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004520349-QTDE052010IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cricoid Arrow marked structure is cricoid cartilage.</p>\n<p><strong>Highyeild:</strong></p><p>Larynx has three unpaired and three paired cartilages. Unpaired: Thyroid, cricoid and epiglottis. Paired: Arytenoid, corniculate and cuneiform.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 19 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 29</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Epistaxis & Deviated Nasal Septum - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 29</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 29 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "TESPAL technique is used in the management of which condition?", "options": [{"label": "A", "text": "CSOM", "correct": false}, {"label": "B", "text": "Epistaxis", "correct": true}, {"label": "C", "text": "Ulcerative tonsillitis", "correct": false}, {"label": "D", "text": "CA Larynx", "correct": false}], "correct_answer": "B. Epistaxis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Epistaxis Transnasal endoscopic sphenopalatine artery ligation. (TESPAL) SPA ligation gives high success in the control of refractory posterior bleeding .</p>\n<p><strong>Highyeild:</strong></p><p>Indication of TESPAL Epistaxis not responding to conventional conservative management. Posterior epistaxis Nasal packing is not needed in TESPAL</p>\n<p><strong>Extraedge:</strong></p><p>The procedure can be done with rigid endoscopes under topical anesthesia with sedation or under general anaesthesia. A mucosal flap is lifted in the posterior part of the lateral nasal wall, sphenopalatine artery (SPA)is localized as it exits the foramen and closed with a vascular clip. Distal branches of the artery can be additionally cauterized and the flap then reposited.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A student presented with complaints of facial pain, nasal discharge, headache, and nasal bleeding. He has a history of recurrent sinusitis in the past. On anterior rhinoscopy examination, the nasal septum is deviated. Which of the following is the treatment of choice?", "options": [{"label": "A", "text": "Submucous Resection", "correct": false}, {"label": "B", "text": "Septoplasty", "correct": true}, {"label": "C", "text": "Functional endoscopic sinus surgery", "correct": false}, {"label": "D", "text": "Rhinoplasty", "correct": false}], "correct_answer": "B. Septoplasty", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Septoplasty The given clinical scenario is a case of deviated nasal septum and septoplasty is the treatment of choice.</p>\n<p><strong>Highyeild:</strong></p><p>DEVIATED NASAL SEPTUM CLINICAL FEATURES Nasal obstruction/blockade is mainly on the same side of Deviation Anosmia Bleeding Pain in the nose, Foul smell, discharge (Rhinorrhoea) Diagnosis : Clinically by Anterior Rhinoscopy. Cottle’s test table,tr,th,td {border:1px solid black;} SUBMUCOSAL RESECTION SEPTOPLASTY Killian's incision Incision applied on both sides Deviated part is resected Complications more common Always done after 17yrs of age Freer's/Transfixation incision Applied only on one side Repaired Complications less common Preferred after 17yrs of age but can be done before also</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Submucous resection can be done but it has more complications, so not the treatment of choice. Option C- Functional endoscopic sinus surgery is done for polyp removal. Option D- Rhinoplasty is done for nasal deformities like saddle nose, and hump nose.</p>\n<p><strong>Extraedge:</strong></p><p>Cottle test: On pulling the cheek away from the mid-line, the nasal valve opens, increasing the airflow from that side of the nasal cavity.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A final-year student is examining a case of deviated nasal septum. Which of the following findings is unlikely to be seen in this patient?", "options": [{"label": "A", "text": "Hypertrophy of turbinates", "correct": false}, {"label": "B", "text": "Nasal Polyp", "correct": true}, {"label": "C", "text": "Epistaxis", "correct": false}, {"label": "D", "text": "Maxillary sinus tenderness", "correct": false}], "correct_answer": "B. Nasal Polyp", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Nasal Polyp The nasal polyp will not be seen in a patient with deviated nasal septum.</p>\n<p><strong>Highyeild:</strong></p><p>DEVIATED NASAL SEPTUM CLINICAL FEATURES Nasal obstruction/blockade is mainly on the same side of Deviation Anosmia Bleeding Pain in the nose, Foul smell, discharge (Rhinorrhoea) Diagnosis: Clinically by Anterior Rhinoscopy. Cottle’s test Cottle test: On pulling the cheek away from the mid- line, the nasal valve opens, increasing the airflow from that side of the nasal cavity.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Compensatory hypertrophy of turbinates on the roomier side is seen in deviated nasal septum. Option C- Epistaxis from vessels over spur or due to crusting of the nasal mucosa may be seen in deviated nasal septum. Option D- Sinusitis is seen due to obstruction of Ostia and poor ventilation of sinuses</p>\n<p><strong>Extraedge:</strong></p><p>Cottle test: On pulling the cheek away from the mid-line, the nasal valve opens, increasing the airflow from that side of the nasal cavity.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 5 year-old boy fell from the bed and developed swelling in the nose and presents to you with a complaint of difficulty in breathing in the department of ENT. Which of the following is the best management for this child?", "options": [{"label": "A", "text": "Observation", "correct": false}, {"label": "B", "text": "Systemic Antibiotics", "correct": false}, {"label": "C", "text": "Surgical Drainage", "correct": true}, {"label": "D", "text": "Nasal Packing", "correct": false}], "correct_answer": "C. Surgical Drainage", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Surgical Drainage The given clinical scenario with swelling in the nose after trauma is suggestive of septal hematoma and it needs immediate surgical drainage.</p>\n<p><strong>Highyeild:</strong></p><p>SEPTAL HEMATOMA Most common presentation - Bilateral nasal obstruction May be associated with frontal headache and a sense of pressure over the nasal bridge. Treatment of septal hematoma: Aspiration of small hematomas Incision and drainage of large hematomas followed by nasal packing Systemic antibiotics to prevent abscess formation</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Septal hematoma should be immediately drained so observation is not the right choice. Option B- Systemic antibiotics are given to prevent abscess formation after drainage is done. Option D- Nasal packing is done after incision and drainage of hematoma.</p>\n<p><strong>Extraedge:</strong></p><p>Small haematomas can be aspirated with a wide-bore sterile needle. Larger hematomas are incised and drained by a small anteroposterior incision parallel to the nasal floor. Septal haematoma, if not drained, may organize into fibrous tissue leading to a permanently thickened septum. If secondary infection supervenes, it results in a septal abscess with necrosis of cartilage and depression of the nasal dorsum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are an ENT surgeon performing septoplasty in a patient with deviated nasal septum. Which of the following is not an indication of the surgery?", "options": [{"label": "A", "text": "Persistent nasal obstruction", "correct": false}, {"label": "B", "text": "Recurrent Sinusitis", "correct": false}, {"label": "C", "text": "Sleep Apnoea", "correct": false}, {"label": "D", "text": "Nasal Discharge", "correct": true}], "correct_answer": "D. Nasal Discharge", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Nasal Discharge Nasal discharge is not an indication for surgery in a patient with deviated nasal septum (DNS).</p>\n<p><strong>Highyeild:</strong></p><p>Indications for surgery in DNS: Symptoms of nasal obstruction and recurrent headaches Recurrent sinusitis and otitis media Recurrent epistaxis from the septal spur Septal deviation causing sleep apnea or hypopnoea syndrome DEVIATED NASAL SEPTUM CLINICAL FEATURES Nasal obstruction/blockade is mainly on the same side of Deviation Anosmia Bleeding Pain in the nose, Foul smell, discharge (Rhinorrhoea) Diagnosis: Clinically by Anterior Rhinoscopy. Cottle’s test</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Symptoms of nasal obstruction and recurrent headaches are an indication of surgery. Option B- Recurrent sinusitis and otitis media is also an indication for surgery. Option C- Septal deviation causing sleep apnea or hypopnoea syndrome is also an indication in DNS.</p>\n<p><strong>Extraedge:</strong></p><p>Cottle test: On pulling the cheek away from the mid-line, the nasal valve opens, increasing the airflow from that side of the nasal cavity.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 34-year-old patient diagnosed with deviated nasal septum is undergoing submucous resection. What is the key incision used in this procedure?", "options": [{"label": "A", "text": "Killian's Incision", "correct": true}, {"label": "B", "text": "Shobinger Incision", "correct": false}, {"label": "C", "text": "Freer's Incision", "correct": false}, {"label": "D", "text": "Weber Ferguson incision", "correct": false}], "correct_answer": "A. Killian's Incision", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Killian's Incision Killian's incision is used in submucosal resection . It is a slightly curvilinear incision , taken 2-3 mm above the caudal end of septal cartilage on the concave side of the deviated nasal septum.</p>\n<p><strong>Highyeild:</strong></p><p>Septal incisions. (A) Killian's incision. (B) Hemitransfixion incision.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B- Shobinger incision is used in modified radical neck dissection. Option C- Freer's hemitransfixation incision is used in septoplasty. It is a septocolumellar incision taken between the caudal end of septal cartilage and columella. Option D- Weber Ferguson incision is used in maxillectomy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 43-year-old female presented to the ent department with foul-smelling nasal discharge and obstruction. On examination, crusts are present bilaterally and on the removal of the crust, bleeding is present. Which of the following is least likely to cause this?", "options": [{"label": "A", "text": "Syphilis", "correct": false}, {"label": "B", "text": "Wegener's Granulomatosis", "correct": false}, {"label": "C", "text": "Deviated nasal septum", "correct": true}, {"label": "D", "text": "Rhinoscleroma", "correct": false}], "correct_answer": "C. Deviated nasal septum", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Deviated nasal septum The given clinical scenario is suggestive of bilateral atrophic rhinitis . Deviated nasal septum causes unilateral atrophic rhinitis .</p>\n<p><strong>Highyeild:</strong></p><p>Indications for surgery in DNS: Symptoms of nasal obstruction and recurrent headaches Recurrent sinusitis and otitis media Recurrent epistaxis from the septal spur Septal deviation causing sleep apnea or hypopnoea syndrome DEVIATED NASAL SEPTUM CLINICAL FEATURES Nasal obstruction/blockade is mainly on the same side of Deviation Anosmia Bleeding Pain in the nose, Foul smell, discharge (Rhinorrhoea) Diagnosis: Clinically by Anterior Rhinoscopy. Cottle’s test</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Syphilis cause bilateral atrophic rhinitis Option B- Wegener's Granulomatosis causes bilateral atrophic rhinitis Option D- Rhinoscleroma also causes bilateral atrophic rhinitis</p>\n<p><strong>Extraedge:</strong></p><p>Cottle Test: On pulling the cheek away from the mid-line, the nasal valve opens, increasing the airflow from that side of the nasal cavity.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Onodi cells and Haller cells are seen in relation to:", "options": [{"label": "A", "text": "Optic nerve and floor of orbit", "correct": true}, {"label": "B", "text": "Optic nerve and internal carotid artery", "correct": false}, {"label": "C", "text": "Optic nerve and nasolacrimal duct", "correct": false}, {"label": "D", "text": "Orbital floor and nasolacrimal duct", "correct": false}], "correct_answer": "A. Optic nerve and floor of orbit", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Optic nerve and floor of orbit The Onodi and Haller cells are ethmoidal air cells. The onodi cell is related to the optic nerve. The Haller cell is related to the floor of the orbit.</p>\n<p><strong>Highyeild:</strong></p><p>Ethmoidal air cells They vary from 8 to 18 in number and lie within the lateral part of the ethmoid bone (between the nasal cavity and orbit) called an ethmoidal labyrinth. Ethmoidal sinuses are divided into 2 groups : (Note earlier there were 3 groups): Anterior Posterior Now the middle group is incorporated into the anterior group.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All of the following statements about septoplasty are true except", "options": [{"label": "A", "text": "Done in cases of symptomatic nasal obstruction", "correct": false}, {"label": "B", "text": "Freer’s incision is used", "correct": false}, {"label": "C", "text": "Mucoperichondrial/mucoperiosteal flap is raised on both sides", "correct": true}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "C. Mucoperichondrial/mucoperiosteal flap is raised on both sides", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mucoperichondrial/mucoperiosteal flap is raised on both sides Septoplasty is a conservative approach to septal surgery; the Mucoperichondrial/periosteal flap is generally raised only on one side.</p>\n<p><strong>Highyeild:</strong></p><p>Indications of septoplasty Deviated septum causing a nasal obstruction on one or both sides. As a part of septorhinoplasty for cosmetic reasons. Recurrent epistaxis is usually from the spur. Sinusitis due to septal deviation. Septal deviation making contact with the lateral nasal wall and causing headaches Killian's and Freer's incision.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Deviated septum causing a nasal obstruction on one or both sides is one of the indications of septoplasty. OPTION B- In cases of a deviated septum, make a slightly curvilinear incision, 2–3 mm above the caudal end of septal cartilage on the concave side (Killian’s incision). In case of caudal dislocation, a transfixion or hemitransfixion (Freer’s) incision is made.</p>\n<p><strong>Extraedge:</strong></p><p>CONTRAINDICATIONS OF SEPTOPLASTY Acute nasal or sinus infection. Untreated diabetes. Hypertension. Bleeding diathesis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The Image depicts", "options": [{"label": "A", "text": "Epley's Maneuver", "correct": false}, {"label": "B", "text": "Heimlich Maneuver", "correct": false}, {"label": "C", "text": "Cottle's Test", "correct": true}, {"label": "D", "text": "Trotter's Method", "correct": false}], "correct_answer": "C. Cottle's Test", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996239504-QTDE011013IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cottle's Test The image above shows Cottle’s test. It is used to test nasal obstruction due to abnormality of the nasal valve as in case of deviated nasal septum.</p>\n<p><strong>Highyeild:</strong></p><p>COTTLE’S TEST It is used to test nasal obstruction due to abnormality of the nasal valve as in the case of deviated nasal septum. In this test, the cheek is drawn laterally while the patient breathes quietly. If the nasal airway improves on the test side, the test is positive and indicates an abnormality of the vestibular component of the nasal valve.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Epley's Maneuver is done for BENIGN PAROXYSMAL POSITIONAL VERTIGO (BPPV) Option B- Heimlich Maneuver is done for foreign bodies in the trachea. Option D- Trotter's Method is done in epistaxis.</p>\n<p><strong>Extraedge:</strong></p><p>Cottle Test: On pulling the cheek away from the mid-line, the nasal valve opens, increasing the airflow from that side of the nasal cavity.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Intractable epistaxis can require ligation of all vessels except", "options": [{"label": "A", "text": "Internal Carotid Artery", "correct": true}, {"label": "B", "text": "External Carotid Artery", "correct": false}, {"label": "C", "text": "Anterior ethmoidal artery", "correct": false}, {"label": "D", "text": "Internal Maxillary Artery", "correct": false}], "correct_answer": "A. Internal Carotid Artery", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Internal Carotid Artery The internal carotid artery is not ligated in intractable epistaxis.</p>\n<p><strong>Highyeild:</strong></p><p>In uncontrollable epistaxis ligation of the following vessels is done External carotid artery Maxillary artery Ethmoidal artery</p>\n<p><strong>Extraedge:</strong></p><p>WOODRUFF’S PLEXUS It is a plexus of veins situated inferior to the posterior end of the inferior turbinate. It is a site of posterior epistaxis in adults.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Most appropriate management of septal hematoma:", "options": [{"label": "A", "text": "Antibiotics", "correct": false}, {"label": "B", "text": "Surgical Drainage", "correct": true}, {"label": "C", "text": "No Treatment", "correct": false}, {"label": "D", "text": "Antihistaminic", "correct": false}], "correct_answer": "B. Surgical Drainage", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Surgical Drainage Small haematomas can be aspirated with a wide-bore sterile needle. Larger haematomas are incised and drained by a small anteroposterior incision parallel to the nasal floor.</p>\n<p><strong>Highyeild:</strong></p><p>SEPTAL HEMATOMA Most common presentation - Bilateral nasal obstruction May be associated with frontal headache and a sense of pressure over the nasal bridge. Treatment of septal hematoma: Aspiration of small hematomas Incision and drainage of large hematomas followed by nasal packing Systemic antibiotics to prevent abscess formation</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Systemic antibiotics should be given to prevent septal abscesses. OPTION C- Septal hematoma must be drained as soon as possible. OPTION D- Antihistaminics have no role .</p>\n<p><strong>Extraedge:</strong></p><p>Small haematomas can be aspirated with a wide-bore sterile needle. Larger haematomas are incised and drained by a small anteroposterior incision parallel to the nasal floor. Septal haematoma, if not drained, may organize into fibrous tissue leading to a permanently thickened septum. If secondary infection supervenes, it results in a septal abscess with necrosis of cartilage and depression of the nasal dorsum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Epistaxis even after ligating the external carotid artery is due to which vessel?", "options": [{"label": "A", "text": "Anterior Ethmoidal Artery", "correct": true}, {"label": "B", "text": "Superior Labial Artery", "correct": false}, {"label": "C", "text": "Sphenopalatine Artery", "correct": false}, {"label": "D", "text": "Greater Palatine Artery", "correct": false}], "correct_answer": "A. Anterior Ethmoidal Artery", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Anterior Ethmoidal Artery Superior labial, sphenopalatine, and greater palatine arteries are branches of the External carotid artery. So if there is bleeding even after ECA ligation it should be from the internal carotid system e. amongst the given options Anterior ethmoidal artery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following do not contribute to the formation of nasal septum?", "options": [{"label": "A", "text": "Palatine Bone", "correct": false}, {"label": "B", "text": "Ethmoid Bone", "correct": false}, {"label": "C", "text": "Vomer", "correct": false}, {"label": "D", "text": "Lacrimal Bone", "correct": true}], "correct_answer": "D. Lacrimal Bone", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lacrimal Bone Lacrimal bone doesn’t contribute to the formation of the nasal septum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are complications of DNS except", "options": [{"label": "A", "text": "Maxillary Sinusitis", "correct": false}, {"label": "B", "text": "Compensatory nasal turbinate hypertrophy", "correct": false}, {"label": "C", "text": "Epistaxis", "correct": false}, {"label": "D", "text": "Spur", "correct": true}], "correct_answer": "D. Spur", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Spur Spur is a type of DNS; not a complication.</p>\n<p><strong>Highyeild:</strong></p><p>TYPES OF DNS Anterior dislocation C shaped dislocation S-shaped dislocation Nasal spur or septal spur impinging on turbinate</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Deviated septum may obstruct sinus ostia resulting in poor ventilation of the sinuses. Therefore, it forms an important cause to predispose or perpetuate sinus infections. OPTION B- Compensatory nasal turbinate hypertrophy may also be seen. OPTION C- Mucosa over the deviated part of the septum is exposed to the drying effects of air currents leading to the formation of crusts, which when removed cause bleeding. Bleeding may also occur from vessels over a septal spur.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Identify the instrument.", "options": [{"label": "A", "text": "Killans Speculum", "correct": false}, {"label": "B", "text": "Tilley’s dressing forceps", "correct": false}, {"label": "C", "text": "Thudichum Speculum", "correct": true}, {"label": "D", "text": "Tonsil holding forceps", "correct": false}], "correct_answer": "C. Thudichum Speculum", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996239515-QTDE011019IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Thudichum Speculum The image above is of the Thudichum speculum. It is used for rhinoscopy.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Killans Speculum. OPTION B- Tilley’s dressing forceps. OPTION D- Tonsil holding forceps.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The most common cause of nose bleeding is", "options": [{"label": "A", "text": "Trauma to Little's area", "correct": true}, {"label": "B", "text": "AV Aneurysm", "correct": false}, {"label": "C", "text": "Posterosuperior part of nasal septum", "correct": false}, {"label": "D", "text": "Hiatus Semilunaris", "correct": false}], "correct_answer": "A. Trauma to Little's area", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Trauma to Little's area The most common cause of nose bleeding is trauma to Little’s area/kiesselbach plexus.</p>\n<p><strong>Highyeild:</strong></p><p>LITTLE’S AREA It is situated at the anteroinferior part of the nasal septum. It is the most common site of epistaxis in children and young adults. Factors that increase the risk of epistaxis in children when compared to adults, include the habit of nose picking and increased friability of mucosa due to frequent upper respiratory tract infections.</p>\n<p><strong>Extraedge:</strong></p><p>WOODRUFF’S PLEXUS It is a plexus of veins situated inferior to the posterior end of the inferior turbinate. It is a site of posterior epistaxis in adults.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 70 year aged patient with epistaxis, the patient is hypertensive with BP = 200/100 mm Hg. On examination, no active bleeding was noted, next step of management is", "options": [{"label": "A", "text": "Observation", "correct": true}, {"label": "B", "text": "Internal maxillary artery ligation", "correct": false}, {"label": "C", "text": "Anterior and posterior nasal pack", "correct": false}, {"label": "D", "text": "Anterior nasal pack", "correct": false}], "correct_answer": "A. Observation", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Observation The question itself says that no active bleeding is seen —so no need to do anything just observe the patient and because his B/P is 200/100 mm Hg which is quite high, give him antihypertensive drugs .</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In case of uncontrolled epistaxis, ligation of the internal maxillary artery is to be done in the:", "options": [{"label": "A", "text": "Maxillary Antrum", "correct": false}, {"label": "B", "text": "Pterygopalatine Fossa", "correct": true}, {"label": "C", "text": "At the neck", "correct": false}, {"label": "D", "text": "Medial wall of the orbit", "correct": false}], "correct_answer": "B. Pterygopalatine Fossa", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Pterygopalatine Fossa The internal maxillary artery is ligated at the Pterygopalatine fossa.</p>\n<p><strong>Highyeild:</strong></p><p>LIGATION SITES OF ARTERIES OF EPISTAXIS The sphenopalatine artery is ligated at the Sphenopalatine foramen. The internal maxillary artery is ligated at the Pterygopalatine fossa. The external carotid artery is ligated at /Above the origin of the superior thyroid artery. Ethmoidal arteries are ligated near Between inner canthus of the eye and the midline of the nose.</p>\n<p><strong>Extraedge:</strong></p><p>WOODRUFF’S PLEXUS It is a plexus of veins situated inferior to the posterior end of the inferior turbinate. It is a site of posterior epistaxis in adults.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The causes of epistaxis are all except", "options": [{"label": "A", "text": "Allergic Rhinitis", "correct": true}, {"label": "B", "text": "Foreign Body", "correct": false}, {"label": "C", "text": "Tumor", "correct": false}, {"label": "D", "text": "Hypertension", "correct": false}], "correct_answer": "A. Allergic Rhinitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Allergic Rhinitis Amongst the options given, foreign body, tumor, and hypertension all can lead to epistaxis. Allergic rhinitis doesn’t lead to epistaxis.</p>\n<p><strong>Highyeild:</strong></p><p>Nasal problems leading to epistaxis : nasal trauma viral rhinitis chronic infections of the nose (which lead to crust formation like atrophic rhinitis, rhinitis sicca, TB of the nose) foreign bodies in the nose (maggots and nonliving) neoplasms (hemangioma, papilloma, carcinoma or sarcoma). Two nasal conditions which do not lead to epistaxis: Nasal polyps Allergic rhinitis</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION B- Foreign bodies may lead to epistaxis. OPTION C- neoplasms (hemangioma, papilloma, carcinoma, or sarcoma) may lead to epistaxis. OPTION D- Hypertension may also lead to epistaxis.</p>\n<p><strong>Extraedge:</strong></p><p>Pharyngeal conditions which lead to epistaxis: Adenoiditis Juvenile angiofibroma Malignant tumors</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Posterior epistaxis is commonly seen in", "options": [{"label": "A", "text": "Children with ethmoidal polyps", "correct": false}, {"label": "B", "text": "Foreign bodies of the nose", "correct": false}, {"label": "C", "text": "Hypertension", "correct": true}, {"label": "D", "text": "Nose Picking", "correct": false}], "correct_answer": "C. Hypertension", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Hypertension M/c cause of epistaxis in adults is hypertension M/c site – Woodruff area ( Causes posterior epistaxis).</p>\n<p><strong>Highyeild:</strong></p><p>WOODRUFF PLEXUS It is a plexus of veins situated inferior to the posterior end of inferior turbinate. It is a site of posterior epistaxis in adults. Contributing vessels : Anastomosis between a sphenopalatine artery and posterior pharyngeal artery.</p>\n<p><strong>Extraedge:</strong></p><p>LITTLE’S AREA It is situated at the anteroinferior part of the nasal septum. It is the most common site of epistaxis in children and young adults. Factors that increase the risk of epistaxis in children when compared to adults, include the habit of nose picking and increased friability of mucosa due to frequent upper respiratory tract infections.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An adolescent boy was brought to the emergency department with a nose bleed secondary to trauma. The bleeding started an hour ago and has not stopped. Where is the bleed most likely to be from?", "options": [{"label": "A", "text": "Little's Area", "correct": true}, {"label": "B", "text": "Bony Septum", "correct": false}, {"label": "C", "text": "Superior Tubinate", "correct": false}, {"label": "D", "text": "Lateral wall of nose", "correct": false}], "correct_answer": "A. Little's Area", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Little's Area The bleeding is most likely from Little's area. Little's area, situated at the anteroinferior part of the nasal septum is the most common site of epistaxis in children and young adults. Factors that increase the risk of epistaxis in children when compared to adults, include the habit of nose picking and increased friability of mucosa due to frequent upper respiratory tract infections.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 33-year-old patient comes with a complaint of bleeding from the Nose. Which artery among the following could be the source of bleeding in a case of epistaxis not controlled by external carotid artery ligation?", "options": [{"label": "A", "text": "Sphenopalatine Artery", "correct": false}, {"label": "B", "text": "Greater palatine artery", "correct": false}, {"label": "C", "text": "Facial Artery", "correct": false}, {"label": "D", "text": "Ethmoidal Artery", "correct": true}], "correct_answer": "D. Ethmoidal Artery", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ethmoidal Artery Ethmoidal artery can cause epistaxis not controlled by external carotid artery ligation. The ethmoidal artery is a branch of the internal carotid artery , whereas sphenopalatine, greater palatine and facial artery are branches of the external carotid artery . Hence if the bleeding is not controlled by external carotid artery ligation , the subsequent step would be to ligate the ethmoidal artery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 4-year-old child is bought to the ER by his mother with complaints of a nosebleed. On examination, the bleed was found to be from Kiesselbach's plexus. Which of the following arteries does not contribute to its formation?", "options": [{"label": "A", "text": "Sphenopalatine Artery", "correct": false}, {"label": "B", "text": "Greater palatine artery", "correct": false}, {"label": "C", "text": "Anterior ethmoidal artery", "correct": false}, {"label": "D", "text": "Posterior ethmoidal artery", "correct": true}], "correct_answer": "D. Posterior ethmoidal artery", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Posterior ethmoidal artery The posterior ethmoidal artery does not contribute to the formation of Kiesselbach's plexus . Kiesselbach's plexus is the vascular anastomosis situated at Little's area ( anteroinferior part of the septum). The contributing arteries are as follows: Sphenopalatine and greater palatine branches of the maxillary artery Septal branch of the superior labial artery which in turn is a branch of the facial artery Anterior ethmoidal branch of the ophthalmic division of internal carotid artery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A crooked nose is due to:", "options": [{"label": "A", "text": "Deviated Tip and Septum", "correct": false}, {"label": "B", "text": "Deviated ala", "correct": false}, {"label": "C", "text": "Deviated Septum", "correct": false}, {"label": "D", "text": "Deviated dorsum and septum", "correct": true}], "correct_answer": "D. Deviated dorsum and septum", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Deviated dorsum and septum In a crooked nose , the midline of the dorsum from the frontonasal angle to the tip is curved in a C - or S-shaped manner.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The nasal septum is formed by all except", "options": [{"label": "A", "text": "Turbinate", "correct": true}, {"label": "B", "text": "Vomer", "correct": false}, {"label": "C", "text": "Palatine Bone", "correct": false}, {"label": "D", "text": "Maxilla", "correct": false}], "correct_answer": "A. Turbinate", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Turbinate Turbinate doesn’t form the part of the nasal septum.</p>\n<p><strong>Highyeild:</strong></p><p>Nasal septum is the osseocartilaginous partition between the two halves of the nasal cavity. Its constituents are Osseous part The vomer Rostrum and crest of sphenoid Nasal crest of maxillary bone Perpendicular plate of ethmoid Nasal crest of nasal bone Nasal spine of frontal bone Nasal crest of palatine bone Cartilaginous part Septal (Quadrilateral) cartilage</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Nasal septum perforation occurs in all the following except", "options": [{"label": "A", "text": "Tuberculosis", "correct": false}, {"label": "B", "text": "Nasal Surgery", "correct": false}, {"label": "C", "text": "Syphilis", "correct": false}, {"label": "D", "text": "Rhinosporidiosis", "correct": true}], "correct_answer": "D. Rhinosporidiosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rhinosporidiosis</p>\n<p><strong>Highyeild:</strong></p><p>Septal Perforation Traumatic (m/c cause) Pathological Idiopathic Surgical trauma Septal abscess Nasal myiasis Rhinolith Lupus vulgaris TB Leprosy Syphilis Wegner's granuloma Tumors of nasal septum, e.g. Chondrosarcoma Also know: Recreational drugs like crack or cocaine snorted nasally are becoming increasingly common cause of septal necrosis. - Note: Cause of Perforation of: Bony septum Cartilaginous septum Whole septum Syphilis Lupus Leprosy Tuberculosis Wegner's granuloma</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- Tuberculosis causes perforation of cartilagenous septum. OPTION B- nasal surgery i.e. traumatic cause is the most common cause of nasal septal perforation. OPTION C- Syphilis cause perforation of the Bony septum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The internal nasal valve is shown in the given Image and chooses the incorrect statement:", "options": [{"label": "A", "text": "Is the widest portion of nostril", "correct": true}, {"label": "B", "text": "Is responsible for sinus ventilation", "correct": false}, {"label": "C", "text": "Is responsible for parabolic nasal flow", "correct": false}, {"label": "D", "text": "Upper edge of lower lateral cartilage is an important boundary.", "correct": false}], "correct_answer": "A. Is the widest portion of nostril", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684996239664-QTDE011038IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Is the widest portion of nostril The internal nasal valve , in normal development, is the narrowest portion of the nasal cavity and is bounded by the septum medially , caudal edge of the upper lateral cartilage and head of the inferior turbinate laterally and nasal floor inferiorly .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION B- Nasal valve area is the least cross-sectional area of the nose and regulates airflow and resistance on inspiration and is responsible for sinus ventilation. OPTION C- Nasal valve area Is responsible for parabolic nasal flow. OPTION D- Upper edge of lower lateral cartilage is an important boundary.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Posterior epistaxis occurs from", "options": [{"label": "A", "text": "Wood ruffs plexus", "correct": true}, {"label": "B", "text": "Kiesselbach's Plexus", "correct": false}, {"label": "C", "text": "Little’s Area", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "A. Wood ruffs plexus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Wood ruffs plexus It is a plexus of veins situated inferior to the posterior end of the inferior turbinate. It is a site of posterior epistaxis in adults.</p>\n<p><strong>Highyeild:</strong></p><p>Contributing vessels : Anastomosis between a sphenopalatine artery and posterior pharyngeal artery.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION B- Kiesselbach's Plexus also called Little’s area. It is the vascular area in the anteroinferior part of the nasal septum just above the vestibule. Anterior ethmoidal, sphenopalatine, greater palatine and septal branches of superior labial arteries and their corresponding veins form an anastomosis here. OPTION C- little’s area is the vascular area in the anteroinferior part of the nasal septum just above the vestibule. Anterior ethmoidal, sphenopalatine, greater palatine and septal branches of superior labial arteries and their corresponding veins form an anastomosis here.</p>\n<p><strong>Extraedge:</strong></p><p>LITTLE’S AREA It is situated at the anteroinferior part of the nasal septum. It is the most common site of epistaxis in children and young adults. Factors that increase the risk of epistaxis in children when compared to adults, include the habit of nose picking and increased friability of mucosa due to frequent upper respiratory tract infections.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 39 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 2</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Extracranial Complications & Labyrinthitis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 2</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 2 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 55-year-old patient known case of tubotympanic disease was taken up for surgery and on examining the middle ear, a few chalky white deposits were found between the ossicles. What is the diagnosis?", "options": [{"label": "A", "text": "Otosclerosis", "correct": false}, {"label": "B", "text": "Tympanosclerosis", "correct": true}, {"label": "C", "text": "Myringosclerosis", "correct": false}, {"label": "D", "text": "Candidal infection of the middle ear", "correct": false}], "correct_answer": "B. Tympanosclerosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tympanosclerosis The given clinical scenario is suggestive of tympanosclerosis. It is a sequela of long-standing middle ear disease where there are hyaline deposits within the middle ear , which calcify leading to impaired mobility of ossicles.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Otosclerosis, more aptly called otospongiosis , is a primary disease of the bony labyrinth. In this, one or more foci of irregularly laid spongy bone replace part of the normally dense enchondral layer of the bony otic capsule. Option C- When hyalinization and calcification involve only the tympanic membrane, it is known as myringosclerosis. Option D- In Candidal infection of the middle ear there is a thick and creamish-white discharge.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are posted in the ENT department where you are evaluating a patient for tympanoplasty in the pre-operative ward. Your ENT professor asks which of the following is not required in a routine tympanoplasty procedure. What will be your answer?", "options": [{"label": "A", "text": "Pure tone audiometry", "correct": false}, {"label": "B", "text": "Routine blood counts", "correct": false}, {"label": "C", "text": "CT Temporal Bone", "correct": true}, {"label": "D", "text": "Informed Consent", "correct": false}], "correct_answer": "C. CT Temporal Bone", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>CT Temporal Bone CT scan of the temporal bone is not required in tympanoplasty procedures. It is required only in cases of mastoidectomy . Tympanoplasty is an operation to eradicate the disease in the middle ear and reconstruct the hearing mechanism. It may be combined with mastoidectomy if the disease process demands it.</p>\n<p><strong>Highyeild:</strong></p><p>TYPES OF TYMPANOPLASTY Type I: all 3 ossicles are present after surgery → a.k.a Myringoplasty Type II: 2 ossicles are present after surgery Type III: 1 ossicle is present after surgery a.k.a Myringo stapedopexy, Columella surgery Type IV: Ossicles are present after surgery The graft is placed over the round window and Eustachian tube The oval window is exteriorised Create a small cave called \"CAVUM MINOR\" A Baffle effect Type V: Create a fistula/fenestra over the bulge of the Lateral semi-circular canal Also called Fenestration surgery</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Pure tone audiometry is done in tympanoplasty. Option B- For tympanoplasty routine blood investigations are done. Option D- informed consent should be taken before any procedure on a patient.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 12 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 12</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Extracranial Complications & Mastoiditis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 12</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 12 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Identify the following instrument used in mastoidectomy.", "options": [{"label": "A", "text": "Mollison’s Mastoid Retractor", "correct": true}, {"label": "B", "text": "Lempert’s Endaural Retractor", "correct": false}, {"label": "C", "text": "Myringotome", "correct": false}, {"label": "D", "text": "Jansen’s self-retaining mastoid retractor", "correct": false}], "correct_answer": "A. Mollison’s Mastoid Retractor", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685002995501-QTDE036002IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mollison’s Mastoid Retractor Mollison's mastoid retractor is Used in mastoidectomy to retract soft tissues after incision and elevation of flaps. It is self-retaining and haemostatic</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B - Lempert’s Endaural Retractor Option C - Myringotome Option D- Jansen’s self-retaining mastoid retractor</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The Tobey-Ayer test is done in?", "options": [{"label": "A", "text": "Sigmoid Sinus Thrombosis", "correct": true}, {"label": "B", "text": "Sagittal Sinus Thrombosis", "correct": false}, {"label": "C", "text": "Uncal Herniation", "correct": false}, {"label": "D", "text": "Serous Otitis Media", "correct": false}], "correct_answer": "A. Sigmoid Sinus Thrombosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Sigmoid Sinus Thrombosis Tobey-Ayer test - This is to record CSF pressure by manometer and to see the effect of manual compression of one or both jugular veins. Compression of the vein on the thrombosed side produces no effect while compression of the vein on the healthy side produces a rapid rise in CSF pressure which will be equal to bilateral compression of jugular veins. It is done in Sigmoid sinus or lateral sinus thrombosis.</p>\n<p><strong>Highyeild:</strong></p><p>STAGES OF LATERAL SINUS THROMBOPHLEBITIS Picket fence fever is seen in lateral sinus thrombophlebitis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Citelli’s angle is", "options": [{"label": "A", "text": "Solid Angle", "correct": false}, {"label": "B", "text": "CP Angle", "correct": false}, {"label": "C", "text": "Sinodural Angle", "correct": true}, {"label": "D", "text": "Part of Mac Ewan’s triangle", "correct": false}], "correct_answer": "C. Sinodural Angle", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Sinodural Angle The Sinodural angle , also called Citelli’s angle , is situated between the sigmoid sinus and the middle fossa dura plate.</p>\n<p><strong>Highyeild:</strong></p><p>SINODURAL ANGLE/CITELLI ANGLE</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Solid angle is the area where three bony semicircular canals meet. Option B- CP angle is the cerebellopontine angle. Option D- Mac Ewan’s triangle</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An 8-year-old female girl presented with fever and pain behind the ear. There is a history of chronic ear discharge. The clinical image is shown in the following image. The diagnosis is", "options": [{"label": "A", "text": "Bezold Abscess", "correct": false}, {"label": "B", "text": "Citelli’S Abscess", "correct": false}, {"label": "C", "text": "Post–auricular abscess", "correct": true}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "C. Post–auricular abscess", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685002995816-QTDE036005IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Post–auricular abscess The given Image shows a postauricular abscess . History tells us that there is chronic ear discharge in 8-year-old media. A postauricular abscess is a complication of Otitis media . A postauricular abscess is the commonest abscess that forms over the mastoid.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- In Bezold abscess Pus bursts through the medial side of the tip of the mastoid and collects under the sternomastoid or digastric triangle. Option B- In citelli, an abscess Abscess is formed behind the mastoid more towards the occipital bone (compare postauricular mastoid abscess which forms over the mastoid). Some authors consider the abscess of the digastric triangle, which is formed by tracking pus from the mastoid tip, as Citelli’s abscess.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Identify the incision.", "options": [{"label": "A", "text": "Wilde’S Incision", "correct": false}, {"label": "B", "text": "Rosen’s Incision", "correct": true}, {"label": "C", "text": "Lempert’s Incision", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "B. Rosen’s Incision", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685002998292-QTDE036006IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rosen’s Incision Rosen’s incision is the most commonly used for stapedectomy . It requires the meatus and canal to be wide enough to work. It consists of two parts: A small vertical incision at the 12 o’clock position near the annulus and A curvilinear incision starting at 6 o’clock position to meet the first incision in the posterosuperior region of the canals, 5–7 mm away from the annulus.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Wilde’S Incision also called a post-aural incision. Option C- Lempert’s Incision</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The diagnosis in a patient with 6th nerve palsy, retro-orbital pain and persistent ear discharge is", "options": [{"label": "A", "text": "Gradenigo's Syndrome", "correct": true}, {"label": "B", "text": "Sjögren’s Syndrome", "correct": false}, {"label": "C", "text": "Frey's Syndrome", "correct": false}, {"label": "D", "text": "Rendu Osler Weber disease", "correct": false}], "correct_answer": "A. Gradenigo's Syndrome", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Gradenigo's Syndrome The classical presentation of petrositis is Gradenigo’s syndrome TRIAD INCLUDES Persistent ear Discharge Diplopia (due to VI nerve involvement) Deep-seated orbital or retro-orbital pain (due to Vth nerve involvement).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B- Sjogren syndrome consists of xerostomia, retro-orbital, and keratoconjunctivitis sicca. Option C- In Frey’s syndrome there is sweating and flushing of skin over the parotid area during mastication. It results from parotid surgery. Option D- Rendu Osler Weber disease / hereditary hemorrhagic telengiectasia is an autosomal dominant disorder characterized by multiple mucocutaneous telangiectasias.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Nerve damage in radical mastoidectomy is", "options": [{"label": "A", "text": "Facial Nerve", "correct": true}, {"label": "B", "text": "Cochlear", "correct": false}, {"label": "C", "text": "Vestibular", "correct": false}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "A. Facial Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Facial Nerve Facial paralysis is one of the complications of radical mastoidectomy.</p>\n<p><strong>Highyeild:</strong></p><p>COMPLICATIONS OF RADICAL MASTOIDECTOMY Facial paralysis. Perichondritis of pinna. Injury to the dura or sigmoid sinus. Labyrinthitis, if the stapes get dislocated. Severe conductive deafness of 50 dB or more. This is due to the removal of all ossicles and tympanic membranes. Cavity problems- Twenty-five per cent of the cavities do not heal and continue to discharge, requiring regular aftercare.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 12-year-old boy presents with torticollis, a tender swelling behind the angle of the mandible and fever. He had a history of ear discharge for the past 6 years. Examination of the ear showed purulent discharge and granulations in the ear canal. The most probable diagnosis is", "options": [{"label": "A", "text": "Acute lymphadenitis secondary to otitis externa", "correct": false}, {"label": "B", "text": "Masked Mastoiditis", "correct": false}, {"label": "C", "text": "Bezold Abscess", "correct": true}, {"label": "D", "text": "Parotitis", "correct": false}], "correct_answer": "C. Bezold Abscess", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Bezold Abscess Purulent ear discharge, granulations in the ear canal, and tender swelling between the angle of the mandible and mastoid indicate a Bezold abscess. Torticollis is due to spasm of sternocleidomastoid muscle . In the Bezold abscess, pus bursts through the medial side of the tip of the mastoid.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- In Acute lymphadenitis secondary to otitis externa there are no torticollis and granulations. Option B- There is no ear discharge or granulations in masked mastoiditis. Option D- In parotitis, there is associated swelling over the parotid area.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Rahul presented with persistent ear discharge and hearing loss. Modified radical mastoidectomy was done to him. A patient comes back with persistent ear discharge and retro-orbital pain. What is your diagnosis:", "options": [{"label": "A", "text": "Diffuse Serous Labyrinthitis", "correct": false}, {"label": "B", "text": "Purulent Labyrinthitis", "correct": false}, {"label": "C", "text": "Petrositis", "correct": true}, {"label": "D", "text": "Latent Mastoiditis", "correct": false}], "correct_answer": "C. Petrositis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Petrositis Since the air cells of the petrous bone communicate with the mastoid , in any patient, if there is persistent ear discharge following cortical or modified radical mastoidectomy, petrositis should be ruled out by CT and MRI . Also, retroorbital pain is suggestive of petrositis due to the involvement of ophthalmic division of trigeminal).</p>\n<p><strong>Highyeild:</strong></p><p>Gradenigo syndrome is the classical presentation of petrositis and consists of a triad of External rectus palsy (VIth nerve palsy). Deep-seated ear or retroorbital pain (Vth nerve involvement). Persistent ear discharge .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Labyrinthitis per se does not lead to ear discharge once the middle ear and mastoid patient have been cleared by surgery. Moreover, in labyrinthitis, there will be vertigo along with hearing loss. So this is not the complication in the given case. Option B- Labyrinthitis per se does not lead to ear discharge once the middle ear and mastoid disease have been cleared by surgery. Moreover, in labyrinthitis, there will be vertigo along with hearing loss. So this is not the complication in the given case. Option D- Latent mastoiditis or masked mastoiditis can be a consequence of inadequate medical management; but there is no question of it arising after modified radical mastoidectomy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 68-year-old male is scheduled to undergo radical mastoidectomy for malignancy of the middle ear. This procedure will include all except", "options": [{"label": "A", "text": "Closure of the Eustachian tube", "correct": false}, {"label": "B", "text": "Ossicles Removed", "correct": false}, {"label": "C", "text": "Cochlea Removed", "correct": true}, {"label": "D", "text": "Exteriorisation of Mastoid", "correct": false}], "correct_answer": "C. Cochlea Removed", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cochlea Removed Cochlea is not removed in radical mastoidectomy. Radical mastoidectomy is a procedure to eradicate the disease from the middle ear and mastoid without any attempt to reconstruct hearing. THE ONLY INDICATIONS ARE- Malignancy of middle ear When cholesteatoma cannot be removed safely eg if it invades the eustachian tube, round window or perilabrynthine cells. If previous attempts to eradicate cholesteatoma have failed.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Eustachian tube is obliterated by a piece of muscle or cartilage Option B- All remnants of the tympanic membrane, ossicles (except the stapes footplate) and mucoperiosteal lining are removed (Not cochlea) Option D- The diseased middle ear and mastoid are permanently exteriorized for inspection and cleaning.</p>\n<p><strong>Extraedge:</strong></p><p>Steps done in radical mastoidectomy The posterior meatal wall is removed and the entire area of the middle ear, attic, antrum and mastoid is converted into a single cavity, by removing the bridge and lowering the facial ridge. All remnants of the tympanic membrane, ossicles (except the stapes footplate) and mucoperiosteal lining are removed (Not cochlea) The eustachian tube is obliterated by a piece of muscle or cartilage The diseased middle ear and mastoid are permanently exteriorized for inspection and cleaning.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Myringitis bullosa is caused by:", "options": [{"label": "A", "text": "Virion", "correct": false}, {"label": "B", "text": "Fungus", "correct": false}, {"label": "C", "text": "Bacteria", "correct": false}, {"label": "D", "text": "Virus", "correct": true}], "correct_answer": "D. Virus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Virus Myringitis bullosa hemorrhagica is a painful condition. It is probably caused by a virus or Mycoplasma pneumoniae .</p>\n<p><strong>Highyeild:</strong></p><p>Myringitis bullosa hemorrhagica It is a painful condition. Characterized by the formation of hemorrhagic blebs on the tympanic membrane and deep meatus. It is probably caused by a virus or mycoplasma pneumoniae but according to logan turner \"Myringitis bullosa hemorrhagica occurs in the presence of viral infection, usually influenza:' Note: Myringitis granulosa is associated with impacted wax, long-standing foreign body or external ear infection.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Gradenigo's syndrome involves which of the following cranial nerves?", "options": [{"label": "A", "text": "5th Nerve", "correct": true}, {"label": "B", "text": "7th Nerve", "correct": false}, {"label": "C", "text": "8th Nerve", "correct": false}, {"label": "D", "text": "11th Nerve", "correct": false}], "correct_answer": "A. 5th Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>5th Nerve The classical presentation of petrositis is gradenigo’s syndrome TRIAD INCLUDES Persistent ear Discharge Diplopia (due to VI nerve involvement) Deep-seated orbital or retro-orbital pain (due to Vth nerve involvement).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 22 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 12</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Facial Nerve & Vestibulocochlear Nerve Anatomy - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 12</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 12 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "What is the test being done below?", "options": [{"label": "A", "text": "Dix Hallpike Manoeuvre", "correct": true}, {"label": "B", "text": "Epley Manoeuvre", "correct": false}, {"label": "C", "text": "Cottle’s test", "correct": false}, {"label": "D", "text": "Head impulse test", "correct": false}], "correct_answer": "A. Dix Hallpike Manoeuvre", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684998592250-QTDE016001IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Dix Hallpike Manoeuvre The above test shows Dix Hallpike Manoeuvre. This test is particularly useful when the patient complains of vertigo in certain head positions. It also helps to differentiate a peripheral from a central lesion.</p>\n<p><strong>Highyeild:</strong></p><p>Dix Hallpike Manoeuvre METHOD The patient sits on a couch. The examiner holds the patient’s head, turns it 45° to the right, and then places the patient in a supine position so that his head hangs 30° below the horizontal. The patient’s eyes are observed for nystagmus. The test is repeated with the head turned to the left and then again in a straight head-hanging position.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Epley Manoeuvre is used to treat BPPV. Epley's maneuver for BPPV of the posterior canal shows the position of the patient and the corresponding position of otolith debris in the posterior canal. (A) The patient sitting facing forward. (B) The patient lying down in the Dix-Hallpike position with head hanging and turned 45° to the right (the affected ear). (C) Head turned to left Dix-Hallpike position with affected ear up. (D) The head and body both turned as a unit to the unaffected side so that the face is turned to the ground. (E) The patient is made to sit with their head bent forward by 20\". Option: C. Cottle’s test is used in nasal obstruction due to abnormality of the nasal valve. In this test, the cheek is drawn laterally while the patient breathes quietly. If the nasal airway improves on the test side, the test is positive and indicates an abnormality of the vestibular component of the nasal valve. Option: D. A head impulse test is done to check VOR (Vestibulo Ocular Reflex).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The labyrinthine artery is a branch of:", "options": [{"label": "A", "text": "Internal Carotid Artery", "correct": false}, {"label": "B", "text": "Basilar Artery", "correct": false}, {"label": "C", "text": "Posterior Cerebellar Artery", "correct": false}, {"label": "D", "text": "Antero inferior cerebellar artery", "correct": true}], "correct_answer": "D. Antero inferior cerebellar artery", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Antero inferior cerebellar artery The labyrinthine artery is a branch of the anteroinferior cerebellar artery but can sometimes arise from the basilar artery. It supplies the whole of the inner ear.</p>\n<p><strong>Highyeild:</strong></p><p>Divisions of the labyrinthine artery supply blood to various parts of the labyrinth.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A Patient Presents With Hyperacusis, Loss Of Lacrimation, Loss Of Taste Sensation In Anterior 2/3rd Of Tongue, Oedema Extends up Upto Which Level Of Facial Nerve?", "options": [{"label": "A", "text": "Vertical Part", "correct": false}, {"label": "B", "text": "Vertical part beyond nerve to stapedius", "correct": false}, {"label": "C", "text": "Vertical part & beyond nerve to stapedius", "correct": false}, {"label": "D", "text": "Proximal to geniculate ganglion", "correct": true}], "correct_answer": "D. Proximal to geniculate ganglion", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Proximal to geniculate ganglion Proximal to geniculate ganglion sends fibers that will innervate lacrimal glands, submandibular glands, sublingual glands, anterior 2/3rd of the tongue, external auditory meatus, pharynx, stapedius, posterior belly of the digastric muscle, muscles of facial expression. The features given in the question tell us that all these fibers are affected which are supplied by geniculate ganglion</p>\n<p><strong>Highyeild:</strong></p><p>In the question, the patient is presenting with Hyperacusis which means nerve to stapedius is involved and arises from the vertical/descending part of the facial nerve. Loss of lacrimation - . Greater superficial petrosal Nerve is involved which arises from the geniculate ganglion is involved. Loss of taste sensation in the anterior 2/3 of the tongue -. The Chorda tympani nerve is involved which arises from the vertical/descending part of the facial nerve.</p>\n<p><strong>Extraedge:</strong></p><p>Remember: Any lesion will lead to paralysis of all Nerves distal to it and will spare proximal nerves Hence - we will have to look for the most proximal lesion which in this case is geniculate ganglion So lesion is either at or proximal to the geniculate ganglion</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Facial nerve Palsy is seen in:", "options": [{"label": "A", "text": "Seborrheic Otitis Externa", "correct": false}, {"label": "B", "text": "Otomycosis", "correct": false}, {"label": "C", "text": "Malignant Otitis Externa", "correct": true}, {"label": "D", "text": "Eczematous Otitis Externa", "correct": false}], "correct_answer": "C. Malignant Otitis Externa", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Malignant Otitis Externa Malignant otitis externa Infection can spread to the skull base and jugular foramen causing multiple cranial palsies which most common is Facial nerve palsy.</p>\n<p><strong>Highyeild:</strong></p><p>MALIGNANT OTITIS EXTERNA It is an inflammatory condition caused by pseudomonas infection usually in elderly diabetics, or se on immunosuppressive drugs. Diagnosis - Severe otalgia in an elderly diabetic patient with granulation tissue in the external ear canal at its cartilaginous–bony junction should alert the physician of necrotizing otitis externa. A CT scan may show bony destruction but is often not helpful. Gallium-67 is more useful in the diagnosis and follow-up of the patient.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Seborrheic Otitis Externa is associated with seborrhoeic dermatitis of the scalp. Itching is the main complaint. Option: B. The clinical features of otomycosis include intense itching, discomfort or pain in the ear, watery discharge with a musty odor, and ear blockage. The fungal mass may appear white, brown, or black. Option: D. Eczematous Otitis Externa is the result of hypersensitivity to infective organisms or topical ear drops such as chloromycetin or neomycin, etc. It is marked by intense irritation, vesicle formation, oozing, and crusting in the canal.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment consists of: (i) Control of diabetes. (ii) Toilet of the ear canal. Remove discharge, debris, granulations, or any dead tissue or bone. (iii) Antibiotic treatment against the causative organism</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 75-year-old female presented with facial nerve paralysis and hyperacusis followed by a parotid gland abscess. The patient showed good clinical improvement after giving broad-spectrum antibiotics, however, without facial nerve improvement. Further investigation revealed that there was the involvement of motor fibers. Those components supplying muscles are involved. Which component of the facial nerve is involved?", "options": [{"label": "A", "text": "Special Visceral Efferent", "correct": true}, {"label": "B", "text": "General Visceral Efferent", "correct": false}, {"label": "C", "text": "Special Visceral Afferent", "correct": false}, {"label": "D", "text": "General Somatic Afferent", "correct": false}], "correct_answer": "A. Special Visceral Efferent", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Special Visceral Efferent There are two efferent and two afferent pathways. Components of the facial nerve include: Special visceral efferent forms the motor root and supplies all the muscles derived from the second branchial arch , i.e. all the muscles of facial expression , auricular muscles (now vestigial), stylohyoid, posterior belly of digastric, and the stapedius.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. General visceral efferent supplies secretomotor fibers to lacrimal, submandibular, and sublingual glands and the smaller secretory glands in the nasal mucosa and the palate. Option: C. Special visceral afferent brings taste from the anterior two-thirds of the tongue via chorda tympani and soft and hard palate via the greater superficial petrosal nerve. Option: D. General somatic afferent brings general sensation from the concha, posterosuperior part of the external canal, and the tympanic membrane.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 50-year-old female presents to the hospital with facial weakness. She had a history of ear pain for the past 5 days. Yesterday when she was drinking water, she drooled on it. She also noticed her face was markedly asymmetric, with significant weakness on the left side. Physical examination reveals facial muscle weakness on the left side. She was unable to close her eye. The patient is conscious and alert. What is the most likely diagnosis?", "options": [{"label": "A", "text": "Bell’S Palsy", "correct": true}, {"label": "B", "text": "Melkersson Syndrome", "correct": false}, {"label": "C", "text": "Ramsay Hunt Syndrome", "correct": false}, {"label": "D", "text": "Frey’S Syndrome", "correct": false}], "correct_answer": "A. Bell’S Palsy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Bell’S Palsy The history and features described above show Bell’s palsy . The onset is sudden . Bell’s palsy is idiopathic LMN palsy of the facial nerve. The patient is unable to close his eye. On attempting to close the eye, the eyeball turns up and out (Bell phenomenon) . Saliva dribbles from the angle of the mouth. The face becomes Pain in the ear may precede or accompany the nerve paralysis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Melkersson Syndrome is an idiopathic disorder consisting of a triad of facial paralysis, swelling of lips, and fissured tongue. Paralysis may be recurrent. Treatment is the same as for Bell's palsy. Option: C. Ramsay Hunt Syndrome is facial paralysis along with vesicular rash in the external auditory canal and pinna. There may also be anesthesia of the face, giddiness, and hearing impairment due to the involvement of the Vth and VIIIth nerves. Option: D. In Frey’s syndrome there is sweating and flushing of skin over the parotid area during mastication. It results from parotid surgery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 5-year-old boy with recurrent unilateral facial nerve palsy was reviewed. The child was initially referred to the otolaryngology clinic for evaluation of recurrent acute otitis media and hearing loss. He developed four episodes of bilateral acute otitis media within a year. His associated symptoms included otalgia, rhinorrhea, nasal congestion, fever, and left peripheral FNP. High-resolution computed tomography of the temporal bones revealed an incompletely covered tympanic segment of the left facial nerve. What is the condition known as?", "options": [{"label": "A", "text": "Bony Dehiscence", "correct": true}, {"label": "B", "text": "Prolapse of Nerve", "correct": false}, {"label": "C", "text": "Hump", "correct": false}, {"label": "D", "text": "Bifurcation and Trifurcation", "correct": false}], "correct_answer": "A. Bony Dehiscence", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Bony Dehiscence High-resolution computed tomography shows that there is an incompletely covered tympanic segment of the left facial nerve. Dehiscence (absence of bony cover) occurs most commonly in the tympanic segment over the oval window. It also occurs near the region of the geniculate ganglion or in the region of retrofacial mastoid cells. A dehiscent nerve is prone to injury at the time of surgery or gets easily involved in the mastoid and middle ear infection. This is the reason for recurrent unilateral facial nerve palsy in the given question.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Prolapse of nerve-The dehiscent nerve may prolapse over the stapes and make stapes surgery or ossicular reconstruction difficult. Option: C. Hump - The nerve may make a hump posteriorly near the horizontal canal making it to injury while exposing the antrum during mastoid surgery. Option: D. Bifurcation and trifurcation - The vertical part of the facial nerve divides into two or three branches, each occupying a separate canal and exiting through the individual foramen.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient presented with reduced saliva secretion on the left side, Loss of taste to the front two-thirds of the tongue, and associated pain in the tongue. The patient had past history of myringoplasty in the left ear. Which nerve is most likely to be involved?", "options": [{"label": "A", "text": "Nerve To Stapedius", "correct": false}, {"label": "B", "text": "Chorda Tympani", "correct": true}, {"label": "C", "text": "Posterior Auricular Nerve", "correct": false}, {"label": "D", "text": "Greater superficial petrosal nerve", "correct": false}], "correct_answer": "B. Chorda Tympani", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Chorda Tympani Chorda Tympani arises from the middle of the vertical segment, passes between the incus and neck of the malleus, and leaves the tympanic cavity through the petrotympanic fissure . It carries secretomotor fibers to submandibular and sublingual glands and brings taste from the anterior two-thirds of the tongue .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Nerve to Stapedius arises at the level of the second genu and supplies the stapedius muscle. Option: C. Posterior Auricular Nerve supplies muscles of the pinna, the occipital belly of the occipitofrontalis, and communicates with an auricular branch of the vagus. Option: D. Greater Superficial Petrosal Nerve arises from the geniculate ganglion and carries secretomotor fibers to the lacrimal gland and the glands of nasal mucosa and palate.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 24-year-old woman presented with visual deterioration and bilateral papilledema on fundoscopy. MRI revealed a giant schwannoma affecting the vestibulocochlear nerve. The peripheral process of first neurons innervates ______.", "options": [{"label": "A", "text": "Organ of Corti", "correct": true}, {"label": "B", "text": "Cristae Ampullaris", "correct": false}, {"label": "C", "text": "Parotid Gland", "correct": false}, {"label": "D", "text": "Tonsils", "correct": false}], "correct_answer": "A. Organ of Corti", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Organ of Corti The first neurons of the pathway are located in the spiral ganglion. They are bipolar . Their peripheral processes innervate the spiral organ of Corti , while central processes form the cochlear nerve .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Cristae ampullar is innervated by the vestibular part. The vestibular nerve divides into superior and inferior division and singular nerve. They pierce the posterosuperior and posteroinferior quadrants of the fundus to innervate the sensory receptors of the equilibrium-the maculae and cristae ampullar of the membranous labyrinth. Option: C. Parotid gland is supplied by general visceral efferent fibers from the inferior salivary nucleus of the glossopharyngeal nerve. Option: D. Tonsils are supplied by general visceral afferent fibers of the dorsal vagal nucleus of the glossopharyngeal nerve.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 40-year-old male presented with complaints of dizziness, vertigo, balance difficulties, nausea, and vomiting. These symptoms only last for a couple of days, but while present, make it extremely difficult to perform routine activities of daily living. It was diagnosed as vestibular neuritis. In the vestibular part of the eighth nerve, where do the fibers of cristae of the posterior semicircular canal lie?", "options": [{"label": "A", "text": "Foramen Singulare", "correct": true}, {"label": "B", "text": "Internal Acoustic Meatus", "correct": false}, {"label": "C", "text": "Inferior Vestibular Area", "correct": false}, {"label": "D", "text": "Vestibular Nucleus", "correct": false}], "correct_answer": "A. Foramen Singulare", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Foramen Singulare The vestibular receptors are the maculae of the saccule and utricle (for static balance) and the crista ampullar of semicircular ducts. Fibres of crista of posterior semicircular canal lie in foramen singulare .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Fibres from cristae of anterior and lateral semicircular canals and some fibers from the two maculae lie in the superior vestibular area of the internal acoustic meatus. Option: C. Most of the fibers from the maculae of the utricle and saccule lie in the inferior vestibular area. The inferior vestibular area (area cribs media) is in the inferior portion of the fundus of the internal acoustic meatus, lateral to tractus spinalis, for passage of fibers of the saccular nerve. Option: D. The central processes arising from the neurons of the ganglion form the vestibular nerve which ends in the vestibular nuclei.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 28-year-old female had complaints of loss of acuity in distinguishing foreground voices against noisy backgrounds, and difficulty in understanding while the telephone is on her right ear. On examination, Rinne's test is positive, Weber's test is lateralized to the left ear and the pure tone audiometry shows that it is a sensorineural hearing loss. Which end artery accompanies the vestibulocochlear nerve while entering the internal acoustic meatus?", "options": [{"label": "A", "text": "Labyrinthine Artery", "correct": true}, {"label": "B", "text": "Anterior inferior cerebellar artery", "correct": false}, {"label": "C", "text": "Superior Cerebellar Artery", "correct": false}, {"label": "D", "text": "Pontine branches of the basilar artery", "correct": false}], "correct_answer": "A. Labyrinthine Artery", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Labyrinthine Artery The labyrinthine artery accompanies the vestibulocochlear nerve and enters the internal auditory meatus to supply the internal ear . It is an end artery . It is the primary source of blood for the inner ear.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Anterior inferior cerebellar artery arises at the lower border of pons, and passes laterally, supplying the sixth, seventh, and eighth cranial nerves. It then loops over the flocculus of the cerebellum and supplies an anteroinferior aspect of the cerebellum. Option: C. Superior cerebellar artery arises close to the superior border of the pons. It winds posteriorly along the superior border of pons and middle cerebellar peduncle, supplying both. It sends many branches to the superior surface of the cerebellum. The oculomotor nerve (III) passes between the superior cerebellar and posterior cerebral arteries. B: Tentorium and middle fossa dura removed to expose trigeminal nerve (V) Option: D. Pontine branches of the basilar artery are numerous slender branches that pierce the pons both in the medial and lateral parts. As the basilar artery courses through the basilar sulcus on the ventral aspect of the pons, it travels adjacent to the abducens nerve at the lower pontine border and the oculomotor nerve as it ascends more cranially</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is not a landmark for the localization of the facial nerve?", "options": [{"label": "A", "text": "1 cm deep and inferior to the tragal pointer", "correct": false}, {"label": "B", "text": "6 mm to 8 mm deep to the tympanomastoid suture", "correct": false}, {"label": "C", "text": "Antero lateral aspect of the styloid process", "correct": false}, {"label": "D", "text": "Lateral to the posterior belly of the digastric muscle", "correct": true}], "correct_answer": "D. Lateral to the posterior belly of the digastric muscle", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lateral to the posterior belly of the digastric muscle The facial nerve leaves the skull immediately anterior to the attachment of the posterior belly of the digastric ; not lateral. The facial nerve can be exposed by careful dissection in the area immediately anterior to the posterior belly of the digastric in the region of the mastoid process.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. 1 cm deep and inferior to the tragal pointer is a landmark for the identification of facial nerve. Option: B. 6 mm to 8 mm deep to the tympanomastoid suture is also a landmark for the identification of facial nerve. Option: C. Antero lateral aspect of the styloid process is also a landmark for the identification of facial nerves.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 22 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 9</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Facial Nerve Paralysis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 9</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 9 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "In Ramsay Hunt syndrome, all nerves are Involved except:", "options": [{"label": "A", "text": "5", "correct": false}, {"label": "B", "text": "7", "correct": false}, {"label": "C", "text": "8", "correct": false}, {"label": "D", "text": "9", "correct": true}], "correct_answer": "D. 9", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>9 In Ramsay Hunt syndrome/herpes zoster ophthalmicus there is involvement of the facial nerve along with vesicular rash in the external auditory canal. The 5th and 8th cranial nerves are also involved. The 9th cranial nerve is not involved.</p>\n<p><strong>Highyeild:</strong></p><p>Ramsay-Hunt syndrome. Note facial palsy and small vesicles in the concha of the right side. In Ramsay Hunt syndrome there is a herpetic vesicular rash on the cochlea, external auditory canal or pinna with lower motor neuron palsy of the ipsilateral facial nerve. It may be accompanied by anaesthesia of the face, giddiness and hearing impairment due to involvement of the Vth and VIIIth nerves.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option A - 5th cranial nerve may be involved. Option B - 7th cranial nerve is involved. Option C - 8th cranial nerve may also get involved.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Right upper motor neuron lesion of facial nerve causes :", "options": [{"label": "A", "text": "Loss of taste sensation in the right anterior part tongue", "correct": false}, {"label": "B", "text": "Loss of corneal reflex on right side", "correct": false}, {"label": "C", "text": "Loss of wrinkling of forehead on left side", "correct": false}, {"label": "D", "text": "Paralysis of lower facial muscles left side", "correct": true}], "correct_answer": "D. Paralysis of lower facial muscles left side", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Paralysis of lower facial muscles left side It is a General Rule that: UMN lesion cause - Contralateral paresis and LMN lesion cause - ipsilateral paresis . So, the right upper motor neuron lesion of a facial nerve will lead to paresis/deformity of the left side. (Ruling out options “a” and “b”) In Right UMN Palsy – Facial muscles of the opposite side (left side) will be affected . Upper facial (forehead) muscles will be spared . So patient will have paralysis of the lower facial muscles on the contralateral (left) side.</p>\n<p><strong>Highyeild:</strong></p><p>Forehead receives bilateral innervation and is thus saved in supranuclear paralysis. Emotional movements controlled by thalamo-nuclear fibres are also preserved.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option A- As the right UMN is involved so left side will be affected, not the right side. Option B- as the right UMN is involved so left side will be affected, not the right side. Option C- upper/ forehead muscles are spared in UMN palsy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Iatrogenic traumatic facial nerve palsy is most commonly caused during:", "options": [{"label": "A", "text": "Myringoplasty", "correct": false}, {"label": "B", "text": "Stapedectomy", "correct": false}, {"label": "C", "text": "Mastoidectomy", "correct": true}, {"label": "D", "text": "Ossiculoplasty", "correct": false}], "correct_answer": "C. Mastoidectomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mastoidectomy All ear operations run the risk of facial nerve damage, particularly if the nerve is exposed. In particular, a mastoidectomy has a high risk because a sharp cutting rotating burr is used in close proximity to the nerve.</p>\n<p><strong>Highyeild:</strong></p><p>Forehead receives bilateral innervation and is thus saved in supranuclear paralysis. Emotional movements controlled by thalamo-nuclear fibres are also preserved.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Bell's palsy is paralysis of:", "options": [{"label": "A", "text": "UMN V Nerve", "correct": false}, {"label": "B", "text": "UMN VII Nerve", "correct": false}, {"label": "C", "text": "LMN V Nerve", "correct": false}, {"label": "D", "text": "LMN VII Nerve", "correct": true}], "correct_answer": "D. LMN VII Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>LMN VII Nerve Bell’s palsy is acute onset LMN facial palsy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "True about Ramsay-hunt syndrome except:", "options": [{"label": "A", "text": "Involves VII Nerve", "correct": false}, {"label": "B", "text": "May involve the VIII nerve", "correct": false}, {"label": "C", "text": "Surgical removal gives an excellent prognosis", "correct": true}, {"label": "D", "text": "The causative agent is a virus", "correct": false}], "correct_answer": "C. Surgical removal gives an excellent prognosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Surgical removal gives an excellent prognosis It is seen that Ramsay Hunt syndrome patients treated with prednisone and acyclovir within 3 days of onset showed statistically significant improvement . Surgical decompression is not indicated in Ramsay Hunt syndrome.</p>\n<p><strong>Highyeild:</strong></p><p>Ramsay-Hunt syndrome. Note facial palsy and small vesicles in the concha of the right side. In Ramsay Hunt syndrome there is a herpetic vesicular rash on the cochlea, external auditory canal or pinna with lower motor neuron palsy of the ipsilateral facial nerve. It may be accompanied by anaesthesia of the face, giddiness and hearing impairment due to involvement of the Vth and VIIIth nerves.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option A- Ramsay Hunt syndrome involves facial nerves. Option B- 8th nerve may also get involved in Ramsay Hunt syndrome. Option D- etiology of Ramsay Hunt syndrome is a herpes virus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 35-year-old patient comes to the clinic with a complaint of facial paralysis. You suspect Bell’s palsy. Which of the following is false about the treatment of Bell's palsy?", "options": [{"label": "A", "text": "Eye Protection", "correct": false}, {"label": "B", "text": "Physiotherapy of facial muscles", "correct": false}, {"label": "C", "text": "Steroids are contraindicated", "correct": true}, {"label": "D", "text": "Antivirals like acyclovir are used with Steroids", "correct": false}], "correct_answer": "C. Steroids are contraindicated", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Steroids are contraindicated Steroids are the drug of choice for Bell’s palsy. Steroids are not contraindicated. If the patient reports within 1 week, the adult dose of prednisolone is 1 mg/kg/day divided into morning and evening doses for 5 days.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option A- Care of the eye is most important. The eye must be protected against exposure to keratitis. Option B- Physiotherapy with electro-stimulation and massage of the facial muscles gives psychological support to the patient. Option D- Steroids can be combined with acyclovir for Herpes zoster oticus or if viral etiology is suspected in Bell's palsy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 20-year-old man presents to the ENT department with vesicles over the external acoustic meatus with ipsilateral facial palsy of LMN type. The cause is:", "options": [{"label": "A", "text": "Herpes Zoster", "correct": true}, {"label": "B", "text": "Herpes Simplex Virus", "correct": false}, {"label": "C", "text": "Both A and B", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "A. Herpes Zoster", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Herpes Zoster Vesicles over external acoustic meatus along with I/L facial nerve palsy of LMN type suggests Ramsay Hunt syndrome. The most common cause is herpes zoster virus.</p>\n<p><strong>Highyeild:</strong></p><p>Ramsay-Hunt syndrome. Note facial palsy and small vesicles in the concha of the right side. In Ramsay Hunt syndrome there is a herpetic vesicular rash on the cochlea, external auditory canal or pinna with lower motor neuron palsy of the ipsilateral facial nerve. It may be accompanied by anaesthesia of the face, giddiness and hearing impairment due to involvement of the Vth and VIIIth nerves.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option B- simplex is being reported to be associated with Bell's palsy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The most common cause of lower motor neuron facial palsy is:", "options": [{"label": "A", "text": "Cholesteatoma", "correct": false}, {"label": "B", "text": "Cerebello-Pontine Angle Tumors", "correct": false}, {"label": "C", "text": "Bell's Palsy", "correct": true}, {"label": "D", "text": "Postoperative (Ear Surgery)", "correct": false}], "correct_answer": "C. Bell's Palsy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Bell's Palsy The commonest cause of facial palsy in adults is Bell's palsy. It is a unilateral and intranuclear type of palsy. It is also the M/C cause of LMN facial palsy</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is not true about Bell's palsy?", "options": [{"label": "A", "text": "Acute Onset", "correct": false}, {"label": "B", "text": "Always Recurrent", "correct": true}, {"label": "C", "text": "Spontaneous Remission", "correct": false}, {"label": "D", "text": "Increased predisposition in Diabetes Mellitus", "correct": false}], "correct_answer": "B. Always Recurrent", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Always Recurrent Bell’s palsy is not always recurrent. The recurrence rate of Bell's palsy is 4.5-75%.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - OPTION A- Bell’s palsy is acute in onset. OPTION C- spontaneous remission occurs mostly. OPTION D- Several authors have also demonstrated a correlation between diabetes mellitus and Bell's palsy in developing countries.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 19 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 3</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Fractures of Facial Bone & Csf Rhinorrhea - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 3</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 3 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Identify the marked line concerned with maxillary sinus tumours and pick the wrong statement pertaining to it:", "options": [{"label": "A", "text": "Marked line is Ohngren’s line.", "correct": false}, {"label": "B", "text": "It divides maxilla into infra and suprastructure.", "correct": false}, {"label": "C", "text": "It is used to delineate the limits of resectability of a tumour in the maxillary sinus.", "correct": false}, {"label": "D", "text": "Tumours superoposterior to this line have better prognosis.", "correct": true}], "correct_answer": "D. Tumours superoposterior to this line have better prognosis.", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685001683055-QTDE027001IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tumours superoposterior to this line have better prognosis. Growths anteroinferior to this line will have a better prognosis than that posterosuperior to it.</p>\n<p><strong>Highyeild:</strong></p><p>OHNGREN’S CLASSIFICATION An imaginary plane is drawn, extending between the medial canthus of the eye and the angle of the mandible. Growths situated above this plane (superstructural) have a poorer prognosis than those below it (infrastructural). Ohngren's line extends from medial canthus of eye to the angle of mandible. Growths anteroinferior to this plane (infrastruc- tural) have a better prognosis than those posterosuperior to it (supra- structural).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Marked line is Ohngren’s line is a true statement. It extends between the medial canthus of the eye and the angle of the mandible. Option B. It divides maxilla into infra and suprastructure is a true statement. Option C. It is used to delineate the limits of resectability of a tumour in the maxillary sinus is also a true statement.</p>\n<p><strong>Extraedge:</strong></p><p>Lederman’s classification It uses two horizontal lines of Sebileau; one passing through the floors of orbits and the other through floors of antra, thus dividing the area into Suprastructure . Ethmoid, sphenoid and frontal sinuses and the olfactory area of the nose. Mesostructure . Maxillary sinus and the respiratory part of the nose. Infrastructure . Containing alveolar process. This classification further uses vertical lines, extending down the medial walls of the orbit to separate ethmoid sinuses and nasal fossa from the maxillary sinuses.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 50-year male presented in ENT OPD with nasal bleeding with right nasal obstruction from the last 6 months. Biopsy of the tumour showing stromal infiltration. A CT scan shows no bony erosion or infraorbital extension. The likely diagnosis is", "options": [{"label": "A", "text": "Glomus tumour", "correct": false}, {"label": "B", "text": "Ringertz Tumour", "correct": true}, {"label": "C", "text": "Mucormycosis", "correct": false}, {"label": "D", "text": "Angiofibroma", "correct": false}], "correct_answer": "B. Ringertz Tumour", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ringertz Tumour Ringertz tumour aka Inverted papilloma / Schneiderian papilloma / Transitional papilloma . It presents with nasal obstruction, nasal discharge and epistaxis . Histology shows stromal infiltration due to reverse growth. So the correct answer is Ringertz Tumour.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option A. Glomus tumour is a slow-growing benign and vascular tumour with conductive hearing loss. Option C. Mucormycosis is an acute invasive fungal infection involving the nose and paranasal sinuses, where fungal hyphae invade blood vessels causing ischaemic necrosis. Option D. Juvenile nasopharyngeal angiofibroma is seen almost exclusively in males in the age group of 10–20 years. Profuse, recurrent and spontaneous epistaxis is the most common presentation.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Inverted papilloma is characterized by all except", "options": [{"label": "A", "text": "Also called Schneiderian papilloma", "correct": false}, {"label": "B", "text": "Seen more often in females", "correct": true}, {"label": "C", "text": "Presents with epistaxis and nasal obstruction", "correct": false}, {"label": "D", "text": "Originates from the lateral wall of the nose", "correct": false}], "correct_answer": "B. Seen more often in females", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Seen more often in females Inverted papilloma is more often seen in males than in females.</p>\n<p><strong>Highyeild:</strong></p><p>It is a tumour of the nonolfactory mucosa of the nose (Schneiderian membrane) and paranasal sinuses. A most common site of origin is a lateral wall of the nose in the middle meatus; less commonly it arises from the maxillary, frontal or sphenoid sinus. It is so named because hyperplastic papillomatous tissue grows into the stroma rather than in an exophytic manner. Human papillomavirus is thought to be responsible for its aetiology. Clinically, men are affected more than women in the age group of 40–70. It is almost always unilateral and presents with nasal obstruction, nasal discharge and epistaxis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option A. Inverted papilloma also called Schneiderian papilloma is a true statement. Option C. Inverted papilloma presents with epistaxis and nasal obstruction is also a true statement. Option D. Inverted papilloma arises in the Lateral nasal wall in the middle meatus rarely on the septum.</p>\n<p><strong>Extraedge:</strong></p><p>Computed tomography (CT) and magnetic resonance imaging (MRI) shows the location and extent of the lesion. MRI also helps to differentiate associated secretions in the sinus from the actual tumour mass. A biopsy is essential for diagnosis. Treatment. Medial maxillectomy is the treatment of choice. It can be performed by lateral rhinotomy or sublabial degloving approach.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 13 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 7</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Glomus Tumour - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 7</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 7 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "While evaluating a patient with a suspected aural polyp, you apply Siegel's speculum and observe that the mass pulsates vigorously and then blanches. What is this sign called?", "options": [{"label": "A", "text": "Rising Sun Sign", "correct": false}, {"label": "B", "text": "Schwarze's Sign", "correct": false}, {"label": "C", "text": "Lighthouse Sign", "correct": false}, {"label": "D", "text": "Brown's Sign", "correct": true}], "correct_answer": "D. Brown's Sign", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Brown's Sign The sign is called Brown's sign or pulsation sign. The given clinical scenario is suggestive of a glomus tumo u When ear canal pressure is raised with Siegel's speculum, the tumour pulsates vigorously and then blanches, the reverse happens with the release of pressure.</p>\n<p><strong>Highyeild:</strong></p><p>Signs are seen in the tumour : Aquino Sign : On pressing the side of the tumour, the tumour shrinks in size and moves away from TM (fades away) Brown’s Sign : On using Siegel’s speculum, the pressure increase in the external ear and the tumour becomes pale/blanches Rising sun sign: Rising Sun sign is seen when a glomus tumour arises from the floor of the middle ear. Phelp sign - On CT Erosion of the spine of the caroticojugular crest is seen in glomus jugulare tumou It is intact in glomus tympanicum.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Rising Sun sign is seen when a glomus tumour arises from the floor of the middle ear. Option B. Schwarze sign is the reddish hue seen on the promontory through the tympanic membrane in otosclerosis. Option C. Lighthouse sign is a pulsatile discharge from a pin-hole perforation of pars tensa seen in mastoiditis.</p>\n<p><strong>Extraedge:</strong></p><p>Radiation treatment does not cure the tumour but may reduce its vascularity and arrest its growth. Radiation is used for inoperable tumours, residual tumours, and recurrences after surgery or for older individuals where extensive skull base surgery is not indicated. Embolization is used to reduce the vascularity of the tumour before surgery or is the sole treatment in inoperable patients who have received radiation.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are a junior resident in the ENT department. A 50-year-old female patient presents to you with pulsatile tinnitus. She also gives complain of progressive hearing loss and later she was diagnosed to have a glomus tumour in the left ear. All are true about Glomus jugulare tumours except", "options": [{"label": "A", "text": "Common In Females", "correct": false}, {"label": "B", "text": "Causes Conductive Deafness", "correct": false}, {"label": "C", "text": "It is a disease of infancy", "correct": true}, {"label": "D", "text": "It invades the labyrinth, petrous pyramid and mastoid", "correct": false}], "correct_answer": "C. It is a disease of infancy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>It is a disease of infancy Glomus tumour is most commonly seen in middle-aged females . It is not seen in infancy.</p>\n<p><strong>Highyeild:</strong></p><p>GLOMUS TUMOUR It is a slow-growing benign and vascular tumour. F:M- 5:1. Mostly unilateral. Seen most commonly in middle age (40 - 50 yrs) The most common presentation is conductive hearing loss. Others- Pulsatile tinnitus, vertigo Secretory symptoms- due to catecholamine release.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Glomus tumour is common in females is a true statement. Option B. Glomus tumour causes conductive hearing loss is also a true statement. Option D. Glomus tumour may invade labyrinth, petrous pyramid and mastoid is also a true statement.</p>\n<p><strong>Extraedge:</strong></p><p>FISCH CLASSIFICATION Stage A : Confined to the middle ear Stage B : Involve Tympano mastoid, not spreading to infra labyrinthine part Stage C : Involves infra labyrinthine part upto petrous apex Stage D : Intra cranial spread</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 55-year-old woman presented with hearing loss, pulsatile ringing in the right ear, and deviation of the tongue to the right side. MRI revealed a mass in the right jugular fossa. On examination, a pulsatile reddish mass was seen behind the intact tympanic membrane. Which of the following structures is invaded by the mass?", "options": [{"label": "A", "text": "Epitympanum", "correct": false}, {"label": "B", "text": "Mesotympanum", "correct": false}, {"label": "C", "text": "Hypotympanum", "correct": true}, {"label": "D", "text": "Mastoid", "correct": false}], "correct_answer": "C. Hypotympanum", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Hypotympanum Hypotympanum is invaded by the tumour . The given case scenario of hearing loss, pulsatile tinnitus, 12th cranial nerve involvement, and red mass on otoscopy is suggestive of Glomus jugulare. It arises in the adventitia of the dome of the jugular bulb and invades the hypotympanum , jugular foramen, and jugular vein.</p>\n<p><strong>Highyeild:</strong></p><p>GLOMUS JUGULARE- Arise from the jugular bulb. GLOMUS TYMPANICUM- Arise from tympanic plexus. GLOMUS TUMOUR It is a slow-growing benign and vascular tumour. F:M- 5:1. Mostly unilateral. Seen most commonly in middle age (40 - 50 yrs) The most common presentation- is conductive hearing loss. Others- Pulsatile tinnitus, vertigo Secretory symptoms- due to catecholamine release.</p>\n<p><strong>Extraedge:</strong></p><p>FISCH CLASSIFICATION Stage A : Confined to the middle ear Stage B : Involve Tympano mastoid, not spreading to infra labyrinthine part Stage C : Involves infra labyrinthine part upto petrous apex Stage D : Intra cranial spread</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 40-year-old female came to ENT OPD with 6 months H/O pulsatile tinnitus in her ear, a feeling of spinning and hearing loss in her left car. She denies fever, chills, cough, and ear or throat pain. She has H/o hypothyroidism and fibromyalgia and is currently on medications like thyroxine and ibuprofen. Her father died of MI at the age of 54. On examination her BP: was 128/70; Pulse was 60/min; respiration was 16/min., and left-sided SN hearing loss. Which of the following is the most likely diagnosis?", "options": [{"label": "A", "text": "Angiofibroma", "correct": false}, {"label": "B", "text": "Exostosis", "correct": false}, {"label": "C", "text": "Glomus Jugulare", "correct": true}, {"label": "D", "text": "Acoustic Neuroma", "correct": false}], "correct_answer": "C. Glomus Jugulare", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Glomus Jugulare The given clinical scenario is suggestive of glomus jugulare. Symptoms of glomus tumours : Hearing loss Tinnitus Bleeding from the ear Vertigo and facial palsy Dysphagia, hoarseness of voice (cranial nerve palsies).</p>\n<p><strong>Highyeild:</strong></p><p>GLOMUS TUMOUR It is a slow-growing benign and vascular tumour. F:M- 5:1. Mostly unilateral. Seen tumour commonly in middle age (40 - 50 yrs) The most common presentation- is conductive hearing loss. Others- Pulsatile tinnitus, vertigo Secretory symptoms- due to catecholamine release.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Angiofibroma is a benign tumour of the nasopharynx. It has a history of profuse recurrent epistaxis. Option B. Exostosis is the most common benign tumour of the external auditory canal. These are usually associated with cold water swimming. Option D. Acoustic neuroma is the most common benign tumour of the cerebellopontine angle. Pulsatile tinnitus is not seen in acoustic neuroma.</p>\n<p><strong>Extraedge:</strong></p><p>FISCH CLASSIFICATION Stage A : Confined to the middle ear Stage B : Involve Tympano mastoid, not spreading to infra labyrinthine part Stage C : Involves infra labyrinthine part upto petrous apex Stage D : Intra cranial spread</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which is the most pulsatile tumour found in the external auditory meatus which bleeds on touch:", "options": [{"label": "A", "text": "Squamous cell ca of the pinna", "correct": false}, {"label": "B", "text": "Basal Cell CA", "correct": false}, {"label": "C", "text": "Adenoma", "correct": false}, {"label": "D", "text": "Glomus Tumor", "correct": true}], "correct_answer": "D. Glomus Tumor", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Glomus Tumor It is worth noting here that though the glomus tumour is the neoplasm of the middle ear , it may perforate the tympanic membrane and appears as a polypus in the external auditory meatus which bleeds profusely if touched.</p>\n<p><strong>Highyeild:</strong></p><p>GLOMUS TUMOUR It is a slow-growing benign and vascular tumour. F:M- 5:1. Mostly unilateral. Seen most commonly in middle age (40 - 50 yrs) The most common presentation- is conductive hearing loss. Others- Pulsatile tinnitus, vertigo Secretory symptoms- due to catecholamine release. x</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Squamous cell carcinoma may present as a painless nodule or an ulcer with raised everted edges and indurated base. Option B. Basal cell carcinoma presents as a nodule with a central crust, the removal of which results in bleeding, But it is not pulsatile in nature. Option C. Adenoma also is not pulsatile in nature and does not bleed on touch.</p>\n<p><strong>Extraedge:</strong></p><p>FISCH CLASSIFICATION Stage A : Confined to the middle ear Stage B : Involve Tympano mastoid, not spreading to infra labyrinthine part Stage C : Involves infra labyrinthine part upto petrous apex Stage D : Intra cranial spread</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 55-year-old is referred to your ENT practice with pulsatile tinnitus, and Brown's sign is positive. The patient gives you her CT films. Which of the following CT signs will help you differentiate between glomus jugulare and glomus tympanicum?", "options": [{"label": "A", "text": "Battle Sign", "correct": false}, {"label": "B", "text": "Lyre Sign", "correct": false}, {"label": "C", "text": "Phelp Sign", "correct": true}, {"label": "D", "text": "Griesinger Sign", "correct": false}], "correct_answer": "C. Phelp Sign", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Phelp Sign Phelp sign helps differentiate between glomus jugulare and glomus tympanicum on CT. It is the erosion of the spine of the carotico-jugular crest , seen in glomus jugulare tumours . It is intact in glomus tympanicum .</p>\n<p><strong>Highyeild:</strong></p><p>Signs seen in Glomus tumour : Aquino Sign : On pressing the side of the tumour, the tumour shrinks in size and moves away from TM (fades away) Brown’s Sign : On using Siegel’s speculum, the pressure increase in the external ear and the tumour becomes pale/blanches Rising sun sign: Rising Sun sign is seen when a glomus tumour arises from the floor of the middle ear. Phelp sign - On CT Erosion of the spine of the caroticojugular crest is seen in glomus jugulare tumou It is intact in glomus tympanicum.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Battle's sign is ecchymosis over the mastoid seen in fractures of the temporal bone. O ption B. Lyre sign is splaying apart of internal and external carotid arteries on angiogram in cases of carotid body tumour of the neck. Option C. Griesinger's sign is edema over the mastoid and is seen in lateral sinus thrombosis.</p>\n<p><strong>Extraedge:</strong></p><p>FISCH CLASSIFICATION Stage A : Confined to the middle ear Stage B : Involve Tympano mastoid, not spreading to infra labyrinthine part Stage C : Involves infra labyrinthine part upto petrous apex Stage D : Intra cranial spread</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Earliest ocular finding in acoustic neuroma:", "options": [{"label": "A", "text": "Diplopia", "correct": false}, {"label": "B", "text": "Ptosis", "correct": false}, {"label": "C", "text": "Loss of corneal sensation", "correct": true}, {"label": "D", "text": "Papilledema", "correct": false}], "correct_answer": "C. Loss of corneal sensation", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Loss of corneal sensation Earliest nerve involved by acou stic neuroma - Vth nerve / trigeminal nerve. Due to Vth nerve involvemen t, there is decreased corneal sensitivity.</p>\n<p><strong>Highyeild:</strong></p><p>GLOMUS TUMOUR It is a slow-growing benign and vascular tumour. F:M- 5:1. Mostly unilateral. Seen most commonly in middle age (40 - 50 yrs) The most common presentation-is conductive hearing loss. Others- Pulsatile tinnitus, vertigo Secretory symptoms- due to catecholamine release.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Diplopia is a late feature in the Glomus tumour. Option B. Ptosis is also a late feature and it occurs when the tumour becomes very large and involves 3rd cranial nerve. Option D. Pappiloedema is a late presentation of vestibular schwannoma due to raised intracranial tension.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 17 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 9</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Granulomatous Diseases of Nose - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 9</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 9 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A patient who works in rice paddy fields presented to emergency with epistaxis and nasal obstruction for the last 2yrs. The examination was suggestive of a pinkish nasal mass studded with whitish dots as shown in the picture. Diagnosis is:", "options": [{"label": "A", "text": "Aspergillosis", "correct": false}, {"label": "B", "text": "Rhinosporidiosis", "correct": true}, {"label": "C", "text": "Mucormycosis", "correct": false}, {"label": "D", "text": "Rhinoscleroma", "correct": false}], "correct_answer": "B. Rhinosporidiosis", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685002240836-QTDE029001IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rhinosporidiosis Given image and examination finding shows that the patient is suffering from Rhinosporidiosis.</p>\n<p><strong>Highyeild:</strong></p><p>Rhinosporodium seeberi was previously considered a fungus. It is now taken as an aquatic protistan protozoan. It belongs to the class mesomycetozoea and is unicellular. This is acquired by contaminated water of ponds also frequented by animals. Features : Leafy Polypoidal mass, pink to purple in colour and attached to the nasal septum or lateral wall The mass is very vascular and bleeds easily on touch Its surface is studded with white dots representing the sporangia of the organism Diagnosis: Biopsy</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. In aspergillosis clinical features are those of acute or subacute rhinitis or sinusitis. A black or greyish membrane is seen in the nasal mucosa. Exploration of the maxillary sinus reveals a fungus ball containing semisolid cheesy white or blackish material. Option:C. a fungal infection of the nose and paranasal sinuses which may prove rapidly fatal. It is seen in uncontrolled diabetics or those taking immunosuppressive drugs. From the nose and sinuses, the infection can spread to the orbit, cribriform plate, meninges and brain. Option: D . Rhinoscleroma is a granulomatous disease caused by Klebsiella Rhinoscleromatis. In it, there is Stage of AtrophyAtrophy of the Nose, Stage of Granuloma formation- WOODY NOSE</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Mikulicz and Russell's bodies are characteristic of:", "options": [{"label": "A", "text": "Rhinoscleroma", "correct": true}, {"label": "B", "text": "Rhinosporidiosis", "correct": false}, {"label": "C", "text": "Plasma Cell Disorders", "correct": false}, {"label": "D", "text": "Lethal Midline Granuloma", "correct": false}], "correct_answer": "A. Rhinoscleroma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rhinoscleroma In Rhinoscleroma Biopsy shows infiltration of submucosa with plasma cells, lymphocytes, eosinophils, Mikulicz cells and Russell bodies . The latter two are diagnostic features of the disease Rhinoscleroma.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOSCLEROMA Organism : KLEBSIELLA RHINOSCLEROMATIS or FRISCH BACILLI Common in SRILANKA In India seen in the Coastal area of South India (State of Tamil Nadu). In North India seen in farmers. STAGES I - Stage of AtrophyAtrophy of Nose II - Stage of Granuloma formation- WOODY NOSE III - Stage of Cicatricial/Sclerotic -Nose is STENOSED, Shape of the nose is changed HEBRA NOSE Diagnostic findings in HPE Mikulicz cell Russell body Treatment Excision of Granulomatous tissue DOC- Streptomycin Rhinoscleroma nose. Rhinoscleroma showing foamy Mikulicz cells (arrow) and lymphocytic infiltration (arrowheads) (H&E, x400). Mikulicz cells contain Gram-negative bacteria which can be better appreciated in sec- tions stained with Giemsa stain and examined under oil immersion lens.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Rhinosporidiosis is caused by:", "options": [{"label": "A", "text": "Fungus", "correct": false}, {"label": "B", "text": "Virus", "correct": false}, {"label": "C", "text": "Bacteria", "correct": false}, {"label": "D", "text": "Protozoa", "correct": true}], "correct_answer": "D. Protozoa", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Protozoa Rhinosporidiosis is a chronic granulomatous infection of mucous membranes . Causative organism is Rhinosporidium seeberi. It Is now taken as an aquatic protozoon.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOSPORIDIOSIS Rhinosporodium seeberi was previously considered a fungus. It Is now taken as an aquatic protistan protozoan. Most commonly affected sites: Nose and nasopharynx Features Young males are more affected (15-40 years). Lesions are polypoid and papillomatous friable masses which bleed easily on touch. They are strawberry (pink to purple) coloured and studded with white dots representing the sporangia. Patients complain of nasal discharge which is blood-tinged. Sometimes frank epistaxis is the only presenting complaint. Diagnosis It is made by biopsy which shows several sporangia and spores. Treatment – Endoscopic excision of the mass followed by cauterization of its base. Recurrence may occur after surgery. Medical management with dapsone decreases the recurrence rate</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "About nasal syphilis the following is true:", "options": [{"label": "A", "text": "Perforation occurs in the septum", "correct": false}, {"label": "B", "text": "Saddle nose deformity may occur", "correct": false}, {"label": "C", "text": "In the newborn, it presents as snuffles", "correct": false}, {"label": "D", "text": "Secondary syphilis is the common association", "correct": true}], "correct_answer": "D. Secondary syphilis is the common association", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Secondary syphilis is the common association Tertiary syphilis is a common association: primary and secondary syphilis are rare associations in nasal syphilitis.</p>\n<p><strong>Highyeild:</strong></p><p>Nasal syphilis may be: Acquired: – Primary, e.g. chancre in the vestibule Secondary, e.g. simple rhinitis, crusting and fissuring leading to atrophic rhinitis Tertiary, e.g. Gumma leads to septal perforation and saddle nose deformity (due to the collapse of the nasal bridge) Congenital: – Early (first 3 months): Presenting as snuffles, purulent nasal discharge, fissuring excoriation. late (around puberty): Gumma in the septum and other stigmata. Tertiary syphilis is a common association: primary and secondary syphilis are rare associations in nasal syphilitis. Septal perforation occurs in the bony part of the septum in the case of syphilis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Septal perforation occurs in the bony part of the septum in case of syphilis. Option: B. In tertiary syphilis, Gumma leads to septal perforation and saddle nose deformity (due to the collapse of the nasal bridge) Option: C. Congenital: – Early (first 3 months): Presenting as snuffles, purulent nasal discharge, fissuring excoriation.</p>\n<p><strong>Extraedge:</strong></p><p>Diagnosis of syphilis is made on serological tests (VDRL) and biopsy of the tissue with special stains to demonstrate Treponema pallidum. Treatment Penicillin is the drug of choice: benzathine penicillin 2.4 million units i.m. every week for 3 weeks with a to- tal dose of 7.2 million units. Nasal crusts are removed by irrigation with an alkaline solution. Bony and cartilaginous sequestra should also be removed. The cosmetic deformity is corrected after the disease becomes inactive.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All of the following are true about Antrochoanal polyps, except:", "options": [{"label": "A", "text": "Single", "correct": false}, {"label": "B", "text": "Unilateral", "correct": false}, {"label": "C", "text": "Premalignant", "correct": true}, {"label": "D", "text": "Arises from the maxillary antrum", "correct": false}], "correct_answer": "C. Premalignant", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Premalignant Antrochoanal polyps are not premalignant . These are non-neoplastic masses.</p>\n<p><strong>Highyeild:</strong></p><p>ANTROCHOANAL POLYPS Unilateral nasal obstruction is the presenting symptom. Obstruction may become bilateral when the polyp grows into the nasopharynx and starts obstructing the opposite choana. The voice may become thick and dull due to hyponasality. Nasal discharge, mostly mucoid, may be seen on one or both sides. As the antrochoanal polyp grows posteriorly, it may be missed on the anterior rhinoscopy. When large, a smooth greyish mass covered with nasal discharge may be seen. It is soft and can be moved up and down with a probe. A large polyp may protrude from the nostril and show a pink congested look on its exposed part An endoscopic view of a choanal polyp on the right side.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Antrochoanal polyps are usually single. Option: B. Unilateral nasal obstruction is the presenting symptom. Option: D. Antrochoanal polyps arise from the maxillary antrum and then grow into the choana and nasal cavity.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following statements is true about rhinolith?", "options": [{"label": "A", "text": "Made up of Tungsten", "correct": false}, {"label": "B", "text": "Stone in ear", "correct": false}, {"label": "C", "text": "Deposition of calcium around a foreign body in the nose", "correct": true}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "C. Deposition of calcium around a foreign body in the nose", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Deposition of calcium around a foreign body in the nose Rhinoliths are calcareous masses which result due to the deposition of salts-like calcium and magnesium carbonates and phosphates around the nucleus of a foreign body.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOLITH It is a stone formation in the nasal cavity. A rhinolith usually forms around the nucleus of a small exogenous foreign body, blood clot or inspissated secretions by slow deposition of calcium and magnesium salts. Rhinoliths are more common in adults. Its common presentation is unilateral nasal obstruction and foul-smelling discharge which is very often bloodstained. On examination, a grey-brown or greenish-black mass with an irregular surface and stony hard feel is seen in the nasal cavity between the septum and turbinates. It is often brittle and a portion of it may break off while manipulating. Sometimes it is surrounded by granulations. They are removed under general anaesthesia.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Rhinoliths are calcareous masses which result due to the deposition of salts-like calcium and magnesium carbonates and phosphates around the nucleus of a foreign body. These don't have tungsten. Option: B. Rhinoliths are stones in the nasal cavity; not in the ear.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Maggots in the nose are best treated by:", "options": [{"label": "A", "text": "Chloroform diluted with water", "correct": true}, {"label": "B", "text": "Liquid Paraffin", "correct": false}, {"label": "C", "text": "Systemic Antibiotics", "correct": false}, {"label": "D", "text": "Lignocaine Spray", "correct": false}], "correct_answer": "A. Chloroform diluted with water", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Chloroform diluted with water Chloroform water or vapour must be instilled in order to anaesthetize or kill the maggots and so release their grip from the skin.</p>\n<p><strong>Highyeild:</strong></p><p>MAGGOTS IN NOSE In the first 3 or 4 days maggots produce intense irritation, sneezing, lacrimation and headache. Thin blood-stained discharge oozes from the nostrils. It is only on the third or fourth day that the maggots may crawl out of the nose. The patient has a foul smell surrounding him. Maggots cause extensive destruction to the nose, sinuses, soft tissue of the face, palate and eyeball. Fistulae may form in the palate or around the nose. Death may occur from meningitis. All visible maggots should be picked up with forceps. Many of them try to retreat into darker cavities when light falls on them. The introduction of chloroform water and oil kills them. (A) The maggot. (B) The fly responsible for maggots.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Liquid Paraffin has no role in the treatment of maggots in the nose. Option: C. Systemic Antibiotics also have no role in the treatment of maggots in the nose. Option: D. Lignocaine Spray also has no role in the treatment of maggots in the nose.</p>\n<p><strong>Extraedge:</strong></p><p>Nasal douche with warm saline is used to remove slough, crusts and dead maggots. A patient with maggots should be isolated with a mosquito net to avoid contact with flies which can perpetuate this cycle. All patients should receive instructions for nasal hygiene before leaving the hospital.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A Rapidly destructive infection of the nose and paranasal sinuses in diabetics is:", "options": [{"label": "A", "text": "Histoplasmosis", "correct": false}, {"label": "B", "text": "Sporotrichosis", "correct": false}, {"label": "C", "text": "Mucormycosis", "correct": true}, {"label": "D", "text": "Sarcoidosis", "correct": false}], "correct_answer": "C. Mucormycosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mucormycosis Mucormycosis is a fungal infection of the nose and paranasal sinuses which may prove rapidly fatal. It is seen in uncontrolled diabetes or those taking immunosuppressive drugs.</p>\n<p><strong>Highyeild:</strong></p><p>MUCORMYCOSIS It is a fungal infection of the nose and paranasal sinuses which may prove rapidly fatal. It is seen in uncontrolled diabetics or those taking immunosuppressive drugs. From the nose and sinuses, the infection can spread to the orbit, cribriform plate, meninges and brain. The rapid destruction associated with the disease is due to the affinity of the fungus to invade the arteries and cause endothelial damage and thrombosis. The typical finding is the presence of a black necrotic mass filling the nasal cavity and eroding the septum and hard palate.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Histoplasmosis is a type of lung infection. It is caused by inhaling Histoplasma capsulatum fungal These spores are found in the soil and the droppings of bats and birds. Option: B. Sporotrichosis, also known as rose handler's disease, is a fungal infection that may be localised to skin, lungs, bone and joints, or become systemic. Option: D. Sarcoidosis is a granulomatous disease of unknown aetiology resembling tuberculosis on histology but with the absence of caseation. It is a systemic disorder and the symptoms may refer to the involvement of the lungs, lymph nodes, eyes or skin.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment is by amphotericin B and surgical debridement of the affected tissues and control of underlying predisposing cause.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Vacuolated Mikulicz cells are pathognomonic features of-", "options": [{"label": "A", "text": "Rhinoscleroma", "correct": true}, {"label": "B", "text": "Rhinosporidiosis", "correct": false}, {"label": "C", "text": "Rhinitis Sicca", "correct": false}, {"label": "D", "text": "Rhinitis Caseosa", "correct": false}], "correct_answer": "A. Rhinoscleroma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rhinoscleroma Mikulicz cells are large foam cells within a central nucleus & vacuolated cytoplasm containing causative bacilli ( Klebsiella rhinoscleromatis ) seen in Rhinoscleroma.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOSCLEROMA Organism : KLEBSIELLA RHINOSCLEROMATIS or FRISCH BACILLI Common in SRILANKA In India seen in the Coastal area of South India (State of Tamil Nadu). In North India seen in farmers. STAGES Stage of AtrophyAtrophy of Nose Stage of Granuloma formation- WOODY NOSE Stage of Cicatricial/Sclerotic -Nose is STENOSED, Shape of the nose is changed HEBRA NOSE Diagnostic findings in HPE Mikulicz cell Russell body</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Rhinosporidiosis is a chronic granulomatous disease caused by Rhinosporidium seeberi. On biopsy shows several sporangia, round or oval in shape and filled with spores. Option: C. Rhinitis Sicca, a crust-forming disease seen in patients who work in dry & hot surroundings. Here the ciliated epithelium undergoes squamous metaplasia with atrophy of seromucinious glands. Option: D. Rhinitis Caseosa is an uncommon condition, where the nose is filled with offensive purulent discharge and cheesy material.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment Excision of Granulomatous tissue DOC- Streptomycin</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 19 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 24</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Hearing Loss Review & Tympanic Membrane Images - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 24</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 24 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Interpret the given audiogram:", "options": [{"label": "A", "text": "Left-sided conductive deafness", "correct": false}, {"label": "B", "text": "Left-side sensorineural deafness", "correct": false}, {"label": "C", "text": "Right-sided conductive deafness", "correct": false}, {"label": "D", "text": "Right-sided sensorineural deafness", "correct": true}], "correct_answer": "D. Right-sided sensorineural deafness", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685002399302-QTDE031002IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Right-sided sensorineural deafness It is right-sided SNHL because the arrows and symbols are red in colour indicating the right side and there is no A – B gap which indicates SNHL.</p>\n<p><strong>Highyeild:</strong></p><p>In CHL: AC is below 25dB and BC is above 25dB And AB gap should be > 15dB spir In SNHL: Both AC and BC Audiogram should be below 25dB and AB gap is < 15dB must be below 25 dB In Mixed Hearing Loss: Both AC and BC Audiogram and AB gap should be > 15dB</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An elderly male comes to an otolaryngologist with complaints of difficulty in hearing. His son reports that he has been raising the radio volume much louder recently. The patient claims that he can hear well when he talks to his wife and son at home, but he has significant difficulty hearing in restaurants or during pujas or other family gatherings, which is why he prefers to stay at home most of the time. He has no history of significant noise exposure. What is the most likely diagnosis?", "options": [{"label": "A", "text": "Otosclerosis", "correct": false}, {"label": "B", "text": "Presbycusis", "correct": true}, {"label": "C", "text": "Middle Ear Effusion", "correct": false}, {"label": "D", "text": "Meniere's Disease", "correct": false}], "correct_answer": "B. Presbycusis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Presbycusis This patient's hearing difficulties are most likely caused by presbycusis, defined as sensorineural hearing loss that occurs with ageing . Patients often complain of difficulty hearing in crowded or noisy environments, similar to what this patient describes.</p>\n<p><strong>Highyeild:</strong></p><p>Presbycusis is a progressive and irreversible bilateral symmetrical age-related sensorineural hearing loss resulting from degeneration of the cochlea or associated structures of the inner ear or auditory nerves. Hearing loss associated with presbycusis is typically first noticed in the sixth decade of life. Patients often complain of difficulty hearing in crowded or noisy environments. CAUSES i) Sensory /Cochlear ii) Neural iii) Metabolic- Stria vascularis gets damaged iv) Mechanical - Tense Basilar membrane v) Mixed</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- Otosclerosis is a type of chronic conductive hearing loss associated with bony overgrowth of the stapes. It typically begins with low-frequency hearing loss and is often found in middle-aged individuals. Option: C- A middle ear effusion, as is seen in patients with serous otitis media, often produces tinnitus and a sensation of pressure in addition to conductive hearing loss. Option: D- Patients with Meniere's disease present with episodes of tinnitus, vertigo, and sensorineural hearing loss which is not seen in the given patient.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Parents of 2 yrs female child with suspected B/L profound hearing loss attended ENT OPD for cochlear implantation. All are investigations needed except", "options": [{"label": "A", "text": "Bera", "correct": false}, {"label": "B", "text": "OAE", "correct": false}, {"label": "C", "text": "PTA", "correct": true}, {"label": "D", "text": "MRI Brain", "correct": false}], "correct_answer": "C. PTA", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>PTA PTA cannot be done in a small child or a baby. For a 2 yr old child or a baby , the screening investigations done for suspected hearing loss are OAE and BERA.</p>\n<p><strong>Highyeild:</strong></p><p>Methods of hearing assessment in infants and children Neonatal screening procedures • ABR/OAES • Arousal test • Auditory response cradle Behaviour observation audiometry • Moro's reflex • Cochleopalpebral reflex • Cessation reflex Distraction techniques (6-18 months) Conditioning techniques (7 months - 2 years) • Visual reinforcement audiometry • Play audiometry (2-5 years) Objective tests • ABR • Otoacoustic emissions • Impedance audiometry Remember in children and infants: Best screening test for detecting hearing loss- Otoacoustic emission Best confirmatory test is BERA. OAE is considered as best screening test as it is less time consuming, easy to perform, child does not need to be sedated and results are available immediately 4.Absent OAE indicates cochlear lesion. If OAE are absent child is taken up for BERA which is confirmatory</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A - BERA is the best confirmatory test for hearing assessment in children and infants. Option: B - OAE is the best screening test for detecting hearing loss. Option: D - MRI is done to check if an enlarged vestibular duct is the cause of hearing loss.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "VEMP checks the integrity of which nerve:", "options": [{"label": "A", "text": "Olfactory nerve", "correct": false}, {"label": "B", "text": "Inferior Vestibular Nerve", "correct": true}, {"label": "C", "text": "Facial Nerve", "correct": false}, {"label": "D", "text": "Glossopharyngeal Nerve", "correct": false}], "correct_answer": "B. Inferior Vestibular Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Inferior Vestibular Nerve VEMP checks the integrity of the inferior and superior vestibular nerves.</p>\n<p><strong>Highyeild:</strong></p><p>VEMP - Visually evoked myogenic potential : 2 Types of VEMP: Cervical VEMP - Tests the saccule From saccule—inferior vestibular—vestibular nuclei— ipsilateral vestibular spinal tract—spinal accessory nerve (CN XI)—sternocleidomastoid Ocular VEMP - Tests the utricle From utricle—superior vestibular nerve—vestibular nuclei—medial longitudinal fasciculus—oculomotor (CNIII) nerve—inferior oblique muscle</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The following audiogram shows which type of hearing loss :", "options": [{"label": "A", "text": "Conductive", "correct": true}, {"label": "B", "text": "Mixed", "correct": false}, {"label": "C", "text": "SNHL", "correct": false}, {"label": "D", "text": "Normal Audiogram", "correct": false}], "correct_answer": "A. Conductive", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685002399383-QTDE031006IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Conductive The given audiogram shows an airborne gap which indicates that the given one is Conductive hearing loss.</p>\n<p><strong>Highyeild:</strong></p><p>In CHL: AC is below 25dB and BC is above 25dB And AB gap should be > 15dB spir In SNHL: Both AC and BC Audiogram should be below 25dB and AB gap is < 15dB must be below 25 dB In Mixed Hearing Loss: Both AC and BC Audiogram and AB gap should be > 15dB In OCHL: AC is below 25dB and BC is above 25dB and AB gap should be > 15dB In SNHL: Both AC and BC Audiogram should be below 25dB and AB gap is < 15dB must be below 25 dB In Mixed Hearing loss: Both AC and BC Audiogram and AB gap should be > 15dB</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: B- In mixed hearing loss both AC and BC audiograms must be below 25 dB and the AB gap should be > 15 dB. Option: C- In SNHL both AC and BC audiograms should be below 25 dB and the AB gap is < 15 dB.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All statements are true about acoustic neuroma, except:", "options": [{"label": "A", "text": "Gamma knife is the treatment of choice", "correct": false}, {"label": "B", "text": "An acute episode of vertigo", "correct": true}, {"label": "C", "text": "The inferior vestibular nerve is the main nerve of origin.", "correct": false}, {"label": "D", "text": "The rollover phenomenon is present", "correct": false}], "correct_answer": "B. An acute episode of vertigo", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>An acute episode of vertigo Acoustic neuroma aka neurilemmoma / vestibular schwannoma / 8th nerve tumour . True vertigo is seldom seen in acoustic neuroma.</p>\n<p><strong>Highyeild:</strong></p><p>Acoustic neuroma aka neurilemmoma / vestibular schwannoma / 8th nerve tumour is a benign, encapsulated, extremely slow-growing tumour of the 8th nerve Age : 40-60 Most common site of acoustic neuroma: – Inferior vestibular nerve > superior vestibular nerve > Cochlear nuclei. (rare) Rollover phenomenon is present i.e. reduction of discrimination score when loudness is increased beyond a particular limit. TREATMENT : Surgery Radiotherapy X knife or gamma knife surgery Cyberknife surgery</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- Gamma knife is the treatment of choice in acoustic neuroma. Option: C- A most common site of acoustic neuroma: – Inferior vestibular nerve Option: D- Rollover phenomenon is present i.e. reduction of discrimination score when loudness is increased beyond a particular limit.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Conductive deafness in longitudinal temporal bone fracture may be due to :", "options": [{"label": "A", "text": "Rupture of the tympanic membrane", "correct": false}, {"label": "B", "text": "Ossicular Disruption.", "correct": false}, {"label": "C", "text": "None of the above.", "correct": false}, {"label": "D", "text": "Both A and B", "correct": true}], "correct_answer": "D. Both A and B", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Both A and B Conductive deafness in longitudinal temporal bone fracture may be due to TM rupture , injury to the tegmen and ossicular disruption.</p>\n<p><strong>Highyeild:</strong></p><p>80% of the temporal bone fracture is of longitudinal type 10-30% are transverse fractures 40-50% of the transverse fractures cause facial nerve injury. Facial nerve involvement is rare with longitudinal fracture</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following tests is recommended for neonatal screening of hearing ?", "options": [{"label": "A", "text": "Automated auditory brainstem response", "correct": false}, {"label": "B", "text": "Spontaneous Oae", "correct": false}, {"label": "C", "text": "Evoked OAE", "correct": true}, {"label": "D", "text": "Distorted Product Oae", "correct": false}], "correct_answer": "C. Evoked OAE", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Evoked OAE In Infants, The best screening test for hearing loss is otoacoustic emissions. Spontaneous OAE is present in only 50% of normal-hearing people The evoked OAE are produced in response to a sound stimulus and is seen in all normal-hearing individuals. Hence we test for evoked OAE. If Evoked OAE are also absent it indicates damage to the outer hair cell. (i.e. cochlear pathology).</p>\n<p><strong>Highyeild:</strong></p><p>Methods of hearing assessment in infants and children table,tr,th,td {border:1px solid black;} Neonatal screening procedures • ABR/OAES • Arousal test • Auditory response cradle Behaviour observation audiometry • Moro's reflex • Cochleopalpebral reflex • Cessation reflex Distraction techniques (6-18 months) Conditioning techniques (7 months - 2 years) • Visual reinforcement audiometry • Play audiometry (2-5 years) Objective tests • ABR • Otoacoustic emissions • Impedance audiometry Remember in children and infants: Best screening test for detecting hearing loss- Otoacoustic emission Best confirmatory test is BERA. OAE is considered as best screening test as it is less time consuming, easy to perform, child does not need to be sedated and results are available immediately Absent OAE indicates cochlear lesion. If OAE are absent child is taken up for BERA which is confirmatory</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A steel factory worker is suffering from noise-induced hearing loss. Which of the following is most likely to be affected?", "options": [{"label": "A", "text": "Inner Hair Cell", "correct": true}, {"label": "B", "text": "Macula", "correct": false}, {"label": "C", "text": "Crista Ampullaris", "correct": false}, {"label": "D", "text": "Saccule", "correct": false}], "correct_answer": "A. Inner Hair Cell", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Inner Hair Cell Brief and loud noises mechanically damaged the organ of Corti, tear Reissner's membrane, rupture hair cells and allow mixing of perilymph and endolymph. A severe blast, in addition, may concomitantly damage the tympanic membrane and disrupt ossicles further adding conductive loss. NIHL causes damage to hair cells, starting in the basal turn of the Outer hair cells are affected before the inner hair cells.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Acoustic neuroma causes what ?", "options": [{"label": "A", "text": "Cochlear Deafness", "correct": false}, {"label": "B", "text": "Retrocochlear Deafness", "correct": true}, {"label": "C", "text": "Conductive Deafness", "correct": false}, {"label": "D", "text": "Any of the above", "correct": false}], "correct_answer": "B. Retrocochlear Deafness", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Retrocochlear Deafness Acoustic neuroma causes retro cochlear deafness. Retrocochlear is used when it is due to the eighth nerve. This is also known as the Eighth nerve tumour.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The best X-ray view of the frontal sinus is", "options": [{"label": "A", "text": "Caldwell", "correct": true}, {"label": "B", "text": "Towne", "correct": false}, {"label": "C", "text": "Water’S", "correct": false}, {"label": "D", "text": "Lateral View", "correct": false}], "correct_answer": "A. Caldwell", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Caldwell Caldwell view (Occipitofrontal view or nose-forehead position). The view is taken with the nose and forehead touching the film and the X-ray beam is projected 15-20 degrees caudally. Frontal sinuses (seen best).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Trotter’s triad is seen in?", "options": [{"label": "A", "text": "Juvenile Nasopharyngeal Angiofibroma", "correct": false}, {"label": "B", "text": "Nasopharyngeal Carcinoma", "correct": true}, {"label": "C", "text": "Choanal Atresia", "correct": false}, {"label": "D", "text": "Lacrimal Duct Obstruction", "correct": false}], "correct_answer": "B. Nasopharyngeal Carcinoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Nasopharyngeal Carcinoma Trotter triad is seen in nasopharyngeal carcinoma.</p>\n<p><strong>Highyeild:</strong></p><p>TROTTER TRIAD INCLUDES Conductive deafness (eustachian tube blockage) Ipsilateral temporoparietal neuralgia (involvement of CN V) Palatal paralysis (CN X)</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is false about the head mirror?", "options": [{"label": "A", "text": "Diameter:9Cm", "correct": false}, {"label": "B", "text": "Central Hole:2Cm", "correct": false}, {"label": "C", "text": "Focal Length:18 Cm", "correct": false}, {"label": "D", "text": "Convex Mirror", "correct": true}], "correct_answer": "D. Convex Mirror", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Convex Mirror The Head mirror is a Concave mirror with a diameter of 9cm , a central aperture of 2cm and a focal length of 18cm approximately It is used to reflect light from the bull's eye lamp onto the part being examined.</p>\n<p><strong>Highyeild:</strong></p><p>HEAD MIRROR</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- The diameter of the head mirror is 9 cm. Option: B- the central hole is 2 cm. Option: C - focal length is 18 cm.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Otoacoustic emissions arise from", "options": [{"label": "A", "text": "Inner Hair Cells", "correct": false}, {"label": "B", "text": "Outer Hair Cell", "correct": true}, {"label": "C", "text": "Both inner and outer hair cells", "correct": false}, {"label": "D", "text": "Organ of Corti", "correct": false}], "correct_answer": "B. Outer Hair Cell", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Outer Hair Cell Otoacoustic emissions (OAE) are low-intensity sounds, which are produced by movements of the outer hair cells of the cochlea. They are produced spontaneously and in response to acoustic stimuli.</p>\n<p><strong>Highyeild:</strong></p><p>Spontaneous OAE are present in only 50% of normal-hearing people The evoked OAE are produced in response to a sound stimulus and is seen in all normal-hearing individuals. Hence we test for evoked OAE. If Evoked OAE are also absent it indicates damage to the outer hair cell. (i.e. cochlear pathology). This non-invasive objective test can diagnose damage to the outer hair cells due to acoustic trauma and ototoxic drugs. It aids in the assessment of hearing in infants.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Primary receptor cells of hearing:", "options": [{"label": "A", "text": "Supporting Cell", "correct": false}, {"label": "B", "text": "Tectorial Membrane", "correct": false}, {"label": "C", "text": "Tunnel of Corti", "correct": false}, {"label": "D", "text": "Hair Cell", "correct": true}], "correct_answer": "D. Hair Cell", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Hair Cell Hair cells: are important receptor cells of hearing and transduce sound energy into electrical energy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All of the following can cause hearing loss except :", "options": [{"label": "A", "text": "Measles", "correct": false}, {"label": "B", "text": "Mumps", "correct": false}, {"label": "C", "text": "Chickenpox", "correct": true}, {"label": "D", "text": "Rubella", "correct": false}], "correct_answer": "C. Chickenpox", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Chickenpox “The most common postnatal cause of acquired SNHL is meningitis, while the most common prenatal cause is intrauterine infection (eg TORCH infections, syphilis, mumps, measles, rubella”.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Conductive hearing loss is seen in all of the following except:", "options": [{"label": "A", "text": "Otosclerosis", "correct": false}, {"label": "B", "text": "Otitis media with effusion", "correct": false}, {"label": "C", "text": "Endolymphatic Hydrops", "correct": true}, {"label": "D", "text": "Suppurative Otitis Media", "correct": false}], "correct_answer": "C. Endolymphatic Hydrops", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Endolymphatic Hydrops Endolymphatic hydrops i.e. Meniere's disease leads to SNHL and not conductive hearing loss. Ménière’s disease , also called endolymphatic hydrops is a disorder of the inner ear where the endolymphatic system is distended with endolymph . It is characterized by (i) vertigo (ii) sensorineural hearing loss (iii) tinnitus (iv) aural fullness.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- Otosclerosis is associated with conductive hearing loss. Option: B- Otitis media with effusion is associated with conductive hearing loss. Option: D- Suppurative Otitis Media is associated with conductive hearing loss.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 55-year-old female presents with tinnitus, dizziness and h/o progressive deafness. Differential diagnosis includes all except", "options": [{"label": "A", "text": "Acoustic Neuroma", "correct": false}, {"label": "B", "text": "Endolymphatic Hydrops", "correct": false}, {"label": "C", "text": "Meningioma", "correct": false}, {"label": "D", "text": "Histiocytosis-X", "correct": true}], "correct_answer": "D. Histiocytosis-X", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Histiocytosis-X The age in question is 55 years, whereas histiocytosis X mostly occur in CHILDREN . It doesn't cause deafness & tinnitus.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- acoustic neuroma is a benign, encapsulated, extremely slow-growing tumour of the VIIIth nerve. Option: B- endolymphatic hydrops/Meniere’s disease is characterized by vertigo, fluctuating hearing loss, tinnitus and a sense of pressure in the involved ear. Option: C- Meningioma is a tumour of the cerebellopontine angle and it may also present with tinnitus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Otitic barotrauma results due to:", "options": [{"label": "A", "text": "Ascent In Air", "correct": false}, {"label": "B", "text": "Descent In Air", "correct": true}, {"label": "C", "text": "Linear Acceleration", "correct": false}, {"label": "D", "text": "Sudden Acceleration", "correct": false}], "correct_answer": "B. Descent In Air", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Descent In Air Otitic Barotrauma: In this condition, the Eustachian tube fails to maintain middle ear pressure at the ambient atmospheric level Etiology - Rapid descent during air flight Underwater diving Compression in the pressure chamber.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- Ascent In Air doesn’t cause otitic barotrauma. Option: C- Linear Acceleration doesn’t cause otitic barotrauma Option: D- Sudden Acceleration doesn’t cause otitic barotrauma</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Fluctuating recurring variable sensorineural deafness is seen in", "options": [{"label": "A", "text": "Serous Otitis Media", "correct": false}, {"label": "B", "text": "Hemotympanum", "correct": false}, {"label": "C", "text": "Peri Labyrinthine Fistula", "correct": true}, {"label": "D", "text": "Labyrinthine Concussion", "correct": false}], "correct_answer": "C. Peri Labyrinthine Fistula", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Peri Labyrinthine Fistula</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "What is your diagnosis ?", "options": [{"label": "A", "text": "Otosclerosis", "correct": false}, {"label": "B", "text": "Congenital Deafness", "correct": false}, {"label": "C", "text": "Noise-induced hearing loss", "correct": true}, {"label": "D", "text": "Meniere's Disease", "correct": false}], "correct_answer": "C. Noise-induced hearing loss", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685002399531-QTDE031026IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Noise-induced hearing loss The given audiogram is of noise-induced hearing loss as it shows a dip at 4000 Hz. The audiogram in NIHL shows a typical notch, at 4 kHz, both for air and bone conduction. It is usually symmetrical on both sides. At this stage, the patient complains of high-pitched tinnitus and difficulty in hearing in noisy surroundings but no difficulty in day-to-day hearing. As the duration of noise exposure increases, the notch deepens and also widens to involve lower and higher frequencies.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 38-year-old gentleman reports decreased hearing in the right ear for the last 2 years. On testing with a 512 Hz tuning fork, the Rinne test is negative on the right ear and positive on the left ear. With Weber's test, the tone is perceived as louder in the left ear. Most likely, the patient has", "options": [{"label": "A", "text": "Right conductive hearing loss", "correct": false}, {"label": "B", "text": "Right severe sensorineural hearing loss", "correct": true}, {"label": "C", "text": "Left sensorineural hearing loss", "correct": false}, {"label": "D", "text": "Left conductive hearing loss", "correct": false}], "correct_answer": "B. Right severe sensorineural hearing loss", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Right severe sensorineural hearing loss Here the patient has right-sided hearing loss in the right ea Rinne is negative so there can be two possibilities- Conductive hearing loss of the right ear Severe sensory neural hearing loss of the right ear which is false negative Rinne TESTS NORMAL CONDUCTIVE LOSS SENSORINEURAL LOSS RINNE AC>BC (Rinne positive) BC>AC (Rinne negative) AC>BC (Rinne positive) WEBER centre Lateralized to the affected ear Lateralized to the normal ear ABSOLUTE BONE CONDUCTION Normal Normal Reduced SCHWABACH TEST Normal Lengthened Shortened</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The \"O\" sign in pure tone audiogram indicates", "options": [{"label": "A", "text": "Air conduction of right ear", "correct": true}, {"label": "B", "text": "Air conduction of left ear", "correct": false}, {"label": "C", "text": "Bone conduction of right ear", "correct": false}, {"label": "D", "text": "Bone conduction of left ear", "correct": false}], "correct_answer": "A. Air conduction of right ear", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Air conduction of right ear O sign indicates air conduction without masking of the right ear.</p>\n<p><strong>Highyeild:</strong></p><p>Symbols used in audiogram charting Right ear Left ear Pencil color Red Blue AC without masking O X AC with masking △ ▯ BC without masking < > BC with masking [ ] No response ↓ ↓ Abbreviations: AC, air conduction; BC, bone conduction</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option:. B - Air conduction of left ear by X sign Option:. C- Bone conduction of right ear by < sign Option:. D - Bone conduction of left ear by > sign</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 30-year-old man presented with pain in the ear, associated with fever and discharge for 2 days. No history of ear infections. O/E tenderness (+) over pinna, serous discharge is seen on otoscopy, tympanic membrane appears normal. Pressure on the tragus leads to excruciating pain. Enlarged pre and post-auricular lymph nodes. What is your diagnosis?", "options": [{"label": "A", "text": "Acute Mastoiditis", "correct": false}, {"label": "B", "text": "Suppurated mastoid lymph nodes", "correct": false}, {"label": "C", "text": "Furunculosis of Meatus", "correct": true}, {"label": "D", "text": "Malignant Otitis Externa", "correct": false}], "correct_answer": "C. Furunculosis of Meatus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Furunculosis of Meatus Absence of preceding otitis media Presents with the painful movement of the pinna Swelling of the cartilaginous part of the meatus Discharge is never mucoid or mucopurulent; the external ear is devoid of mucus-secreting glands. Normal looking TM Enlarged pre and post Auricular lymph nodes.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- Absence of any preceding ear infection excludes the diagnosis of acute mastoiditis, also mastoiditis is associated with tenderness over the mastoid and mucopurulent or purulent discharge which is not seen in this case. Option: B- Suppurated mastoid lymph nodes may be seen secondary to any scalp infection, and are seen usually as a superficial abscess. Option: D- Malignant Otitis externa is seen as a granulomatous infection in an immunocompromised patient.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 34 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 3</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Infective Rhinosinusitis - 1 - Fungal Sinusitis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 3</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 3 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "All of the following are diagnostic criteria of allergic fungal sinusitis (AFS) except?", "options": [{"label": "A", "text": "Areas of High attenuation of CT scan", "correct": false}, {"label": "B", "text": "Orbital Invasion", "correct": true}, {"label": "C", "text": "Allergic Eosinophilic mucin", "correct": false}, {"label": "D", "text": "Type 1 Hypersensitivity", "correct": false}], "correct_answer": "B. Orbital Invasion", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Orbital Invasion Orbital invasion is not the diagnostic criteria of allergic fungal sinusitis .</p>\n<p><strong>Highyeild:</strong></p><p>The following are the diagnostic criteria of Allergic fungal sinusitis. Bent and Kuhn Diagnostic Criteria Major Minor Type I hypersensitivity Nasal polyposis Characteristic CT findings Eosinophilic mucin without invasion Positive fungal stain Asthma Unilateral disease Bone erosion Fungal cultures Charcot-Leyden crystals Serum eosinophilia CT. computed tomography It is an allergic reaction to the causative fungus and presents with sinonasal polyposis and mucin. The latter contains eosinophils, Charcot- Leyden crystals, and fungal hyphae. There is no invasion of the sinus mucosa with fungus. Usually, more than one sinus is involved on one or both sides. Treatment is endoscopic surgical clearance of the sinuses with drainage and ventilation. This is combined with pre and postoperative systemic steroids.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A. Areas of High attenuation of CT scan are primary diagnostic criteria for allergic fungal sinusitis. Option: C. Allergic Eosinophilic mucin is a primary diagnostic criterion for allergic fungal sinusitis. Option: D. Type 1 Hypersensitivity is also a primary diagnostic criterion for allergic fungal sinusitis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Allergic Fungal sinusitis is seen commonly in:", "options": [{"label": "A", "text": "Immunocompetent PT", "correct": true}, {"label": "B", "text": "Immunocompromised Pt", "correct": false}, {"label": "C", "text": "Diabetic PT", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "A. Immunocompetent PT", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Immunocompetent PT Allergic fungal sinusitis is a noninvasive disorder seen in immunocompetent individuals. Etiological agents: Dermaticeous species, Alternaria, Curvularia, Aspergillus</p>\n<p><strong>Highyeild:</strong></p><p>Allergic Fungal Sinusitis is an allergic reaction to the causative fungus and presents with sinonasal polyposis and mucin. The latter contains eosinophils, Charcot- Leyden crystals, and fungal hyphae. There is no invasion of the sinus mucosa with fungus. Usually, more than one sinus is involved on one or both sides. A CT scan shows mucosal thickening with hyperdense areas. There may be sinus expansion or bone erosion due to pressure, but no fungal invasion. Treatment is endoscopic surgical clearance of the sinuses with pro-vision of drainage and ventilation. This is combined with pre and postoperative systemic steroids. CT scan showing allergic fungal sinusitis both sides.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45-year-old lady complained of nasal congestion, stuffiness, discharge, and anosmia. The paranasal sinus CT scan was done as shown below; what is the diagnosis?", "options": [{"label": "A", "text": "Ethmoidal Polyp", "correct": false}, {"label": "B", "text": "Antrochoanal Polyp", "correct": false}, {"label": "C", "text": "Allergic Fungal Sinusitis", "correct": true}, {"label": "D", "text": "Mucormycosis", "correct": false}], "correct_answer": "C. Allergic Fungal Sinusitis", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004470815-QTDE066005IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Allergic Fungal Sinusitis A CT scan shows mucosal thickening with hyperdense areas, and the above symptoms suggest allergic fungal sinusitis.</p>\n<p><strong>Highyeild:</strong></p><p>It is an allergic reaction to the causative fungus & presents with Sino nasal polyposis & mucin. The latter contains eosinophils, Charcot-Leyden crystals & fungal hyphae There is no invasion of the sinus mucosa with fungus Usually, more than one sinus is involved on one or both sides A CT scan shows mucosal thickening with hyperdense areas There may be an expansion of the sinus or bone erosion due to pressure, but no fungal invasion Treatment is endoscopic surgical clearance of the sinuses with the provision of drainage & ventilation. This is combined with pre & postoperative systemic steroid</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A. Ethmoidal polyps are bilateral. Option: B. Antrochoanal polyp is unilateral but commonly seen in children. Option: D. In mucormycosis Typical finding is the presence of a black necrotic mass filling the nasal cavity and eroding the septum and hard palate.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 13 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 3</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Infective Rhinosinusitis Part 2 - Bacterial Sinusitis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 3</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 3 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A ten-year-old girl presented with pain between the eyes, frontal headache, discharge from the nose, post nasal drip, and high fever; what is the provisional diagnosis?", "options": [{"label": "A", "text": "Acute Frontal Sinusitis", "correct": true}, {"label": "B", "text": "Acute Ethmoidal Sinusitis", "correct": false}, {"label": "C", "text": "Acute Sphenoidal Sinusitis", "correct": false}, {"label": "D", "text": "Acute maxillary Sinusitis", "correct": false}], "correct_answer": "A. Acute Frontal Sinusitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Acute Frontal Sinusitis Above history and examination point toward the diagnosis of frontal sinusitis.</p>\n<p><strong>Highyeild:</strong></p><p>CLINICAL FEATURES Frontal headache - Usually severe and localized over the affected sinus. It shows characteristic periodicity, i.e., it comes up on waking, gradually increases, reaches its peak by about mid-day, and then subsides. It is also called “office headache” because of its presence only during office hours. Tenderness -Pressure upwards on the frontal sinus floor, just above the medial canthus, causes exquisite pain. It can also be elicited by tapping over the frontal sinus's anterior wall in the supraorbital region's medial part. Oedema of the upper eyelid with suffused conjunctiva and photophobia. Nasal discharge - A vertical streak of mucopus is seen high up in the anterior part of the middle meatus. This may be absent if the ostium is closed with no drainage. The nasal mucosa is inflamed in the middle meatus. X-rays - The opacity of the affected sinus or fluid level can be seen. Both Waters’ and lateral views should be CT scan is the preferred modality.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. In Acute Ethmoidal Sinusitis, Pain is localized over the bridge of the nose, medial and deep to the eye. Movements of the eyeball aggravate it. Option: C. In Acute Sphenoidal Sinusitis Headache is usually localized to the occiput or vertex. Pain may also be referred to the mastoid region. Option: D. In Acute maxillary Sinusitis, Pain typically is situated over the upper jaw but may be referred to the gums or teeth. For this reason, the patient may primarily consult a dentist. Pain is aggravated by stooping, coughing, or chewing.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The most common three causative bacterial agents of acute sinusitis are:", "options": [{"label": "A", "text": "Streptococcus pneumoniae, Haemophilus influenzae, and Moraxella catarrhalis", "correct": true}, {"label": "B", "text": "Streptococcus pneumoniae, Staphylococcus aureus, and Moraxella catarrhalis", "correct": false}, {"label": "C", "text": "Streptococcus pneumoniae, Haemophilus influenzae, and Staphylococcus aureus", "correct": false}, {"label": "D", "text": "Staphylococcus aureus, Haemophilus influenzae, and Moraxella catarrhalis", "correct": false}], "correct_answer": "A. Streptococcus pneumoniae, Haemophilus influenzae, and Moraxella catarrhalis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Streptococcus pneumoniae, Haemophilus influenzae, and Moraxella catarrhalis “Among community-acquired cases , pneumoniae and non typable Haemophilus influenzae are the most common pathogens , accounting for 50–60% of cases. Moraxella catarrhalis causes disease in a significant percentage (20%) of children but less often in adults. Other streptococcal species and Staphylococcus aureus cause only a tiny percentage of cases. However, there is increasing concern about community strains of methicillin–resistant S. aureus (MRSA) as an emerging cause.” According to Nelson 18th/ed, pp 1749,1750 “The bacterial pathogens causing acute bacterial sinusitis in children and adolescents include Streptococcus pneumoniae (= 30%), non typable Haemophilus influenzae (=20%).”</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 33 years old man with a recent history of upper respiratory tract infection has a headache that worsens during office hours. The headache comes upon waking, tends to be at its worst by about midday, and improves as the day progresses. This characteristic periodicity is seen in?", "options": [{"label": "A", "text": "Maxillary Sinusitis", "correct": false}, {"label": "B", "text": "Frontal Sinusitis", "correct": true}, {"label": "C", "text": "Ethmoid Sinusitis", "correct": false}, {"label": "D", "text": "Sphenoid Sinusitis", "correct": false}], "correct_answer": "B. Frontal Sinusitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Frontal Sinusitis Headache in frontal sinusitis shows characteristic periodicity , i.e., it comes upon waking, gradually increases, reaches its peak by about midday, and then starts subsiding. It is also called an office headache because of its presence only during office hours.</p>\n<p><strong>Highyeild:</strong></p><p>FRONTAL SINUSITIS CLINICAL FEATURES Frontal headache - Usually severe and localized over the affected sinus. It shows characteristic periodicity, i.e., it comes up on waking, gradually increases, reaches its peak by about mid-day, and then subsides. It is also called “office headache” because of its presence only during office hours. Tenderness -Pressure upwards on the frontal sinus floor, just above the medial canthus, causes exquisite pain. It can also be elicited by tapping over the frontal sinus's anterior wall in the supraorbital region's medial part. Oedema of the upper eyelid with suffused conjunctiva and photophobia. Nasal discharge - A vertical streak of mucopus is seen high up in the anterior part of the middle meatus. This may be absent if the ostium is closed with no drainage. The nasal mucosa is inflamed in the middle meatus. X-rays - The opacity of the affected sinus or fluid level can be seen. Both Waters’ and lateral views should be taken. CT scan is the preferred modality.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 13 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 12</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Instruments for EAR - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 12</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 12 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Direction of the water jet while doing syringing of the ear should be:", "options": [{"label": "A", "text": "Anterioinferior", "correct": false}, {"label": "B", "text": "Posterosuperior", "correct": true}, {"label": "C", "text": "Anterosuperior", "correct": false}, {"label": "D", "text": "Posteroinferior", "correct": false}], "correct_answer": "B. Posterosuperior", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Posterosuperior In syringing (done to remove impacted wax) pinna is pulled upwards and backwards, and a stream of water from the ear syringe is directed along the posterosuperior wall of the meatus.</p>\n<p><strong>Highyeild:</strong></p><p>SYRINGING</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All of the following statements regarding this instrument are true except:", "options": [{"label": "A", "text": "Speculum that is used here has a convex lens with a minification of 2.5 times", "correct": true}, {"label": "B", "text": "This device is used to access tympanic membrane mobility", "correct": false}, {"label": "C", "text": "This device is used to elicit a fistula sign.", "correct": false}, {"label": "D", "text": "This device is used to administer antibiotics in the middle ear cavity", "correct": false}], "correct_answer": "A. Speculum that is used here has a convex lens with a minification of 2.5 times", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004557774-QTDE053002IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Speculum that is used here has a convex lens with a minification of 2.5 times The given Image is of Siegle’s speculum . It has a convex lens which produces magnification and not minification.</p>\n<p><strong>Highyeild:</strong></p><p>Siegel’s Speculum Used to assess tympanic membrane mobility. Used to elicit fistula signs. Used to administer antibiotics in the middle ear cavity.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. This device is used to assess the tympanic membrane mobility is a true statement. Option C. This device is used to elicit a fistula sign is a true statement. Option D. This device is used to administer antibiotics in the middle ear cavity is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 4-year-old child presents with fever and earache for five days. On examination, a red congested, bulging tympanic membrane is seen. As a treatment protocol, the ENT surgeon anticipates the impending rupture of the tympanic membrane and plans to make the following incision on the tympanic membrane. Choose the instrument used for the same:", "options": [{"label": "A", "text": "A", "correct": false}, {"label": "B", "text": "B", "correct": true}, {"label": "C", "text": "C", "correct": false}, {"label": "D", "text": "D", "correct": false}], "correct_answer": "B. B", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004558080-QTDE053004IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>B With the history and examination findings given, a diagnosis of acute otitis media is made, and myringotomy is done in case of red bulging TM in case of impending rupture. It is done using myringotomy, shown in option B.</p>\n<p><strong>Highyeild:</strong></p><p>MYRINGOTOMY In ASOM, myringotomy is done in the posteroinferior quadrant by a curvilinear J-shaped incision . In the case of Serous otitis media , myringotomy is done by a radial incision in the anteroinferior quadrant along with grommet insertion .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Killianʼs nasal gouge (bayonet-shaped). Used to remove septal spurs or bony crests and ridges in SMR operation. Option C. Lempertʼs curette (scoop). Used for removal of bony septa and granulations in mastoid surgery. Option D. Sickel knife. Used for procedures like uncinectomy, ethmoidectomy, turbinate reduction and septoplasty procedures.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A child presents with foul-smelling ear discharge for six months. On examination, a large marginal perforation along with cholesteatoma is seen. To perform modified radical mastoidectomy in the above patient, all the following instruments are used except:", "options": [{"label": "A", "text": "A", "correct": false}, {"label": "B", "text": "B", "correct": false}, {"label": "C", "text": "C", "correct": true}, {"label": "D", "text": "D", "correct": false}], "correct_answer": "C. C", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004558526-QTDE053005IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>C Option C is the Boyle-Davis mouth gag . It is not used for modified radical mastoidectomy .</p>\n<p><strong>Highyeild:</strong></p><p>Boyle-Davis Mouth Gag It is used for opening the mouth and depressing the tongue. It is used for various operations on the oral cavity (palate surgery), oropharynx (tonsillectomy surgery of soft palate pharyngoplasty), and nasopharynx (adenoidectomy excision of angiofibroma).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Farabeuf's periosteal elevator. Used for the elevation of periosteum from the mastoid cortex in mastoidectomy. Option B. Mollison`s mastoid retractor. Used in mastoidectomy to retract soft tissues after incision and elevation of qaps. It is self-retaining and haemostatic. Option D. MacEwen's curette and cell seeker. Used in mastoid surgery to explore the air cells with one end and to curette the intervening septa and granulations with the other.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Name the instrument used to remove foreign bodies from ear and ear dressings-", "options": [{"label": "A", "text": "Tilly's Forceps", "correct": true}, {"label": "B", "text": "Luc's Forceps", "correct": false}, {"label": "C", "text": "Denis Browne forceps", "correct": false}, {"label": "D", "text": "Walsham Forceps", "correct": false}], "correct_answer": "A. Tilly's Forceps", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tilly's Forceps Tilley's dressing forceps are used for nasal packing , ear dressing , and removing foreign bodies from the nose. It has a box joint.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. Luc's forceps. Used in Caldwell Luc operation (to remove mucosa), submucosal resection (SMR) operation (to remove bone or cartilage), polypectomy (to grasp and avulse polyps), and to take a biopsy from the nose or throat. Option C. Dennis Browne tonsil holding forceps have cup-shaped tips with holes and are used in tonsillectomy operations. Option D. Walsham's forceps. Used for disimpacting and reducing fractures of the nasal bone.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 26-year-old female patient presented with a history of bilateral hearing loss and tinnitus, which worsened during pregnancy. Pure tone audiometry pndings are given below. An ENT surgeon plans to use the following for the patient. Identify and tell the use of the following:", "options": [{"label": "A", "text": "TORP, Replaces the ossicular chain in the middle ear", "correct": false}, {"label": "B", "text": "Grommet allows the drainage of the middle ear", "correct": false}, {"label": "C", "text": "PORP, Replaces the malleus and incus", "correct": false}, {"label": "D", "text": "Teflon piston replaces the stapes", "correct": true}], "correct_answer": "D. Teflon piston replaces the stapes", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004559389-QTDE053007IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Teflon piston replaces the stapes With the history and audiometry findings given, a diagnosis of otosclerosis is made. Stapedectomy is the surgical procedure of choice for otosclerosis, and the Teflon piston can be used to replace the stapes.</p>\n<p><strong>Highyeild:</strong></p><p>Otosclerosis, more aptly called otospongiosis, is a primary disease of the bony labyrinth. In this, one or more irregularly laid spongy bone foci replace part of the normally dense enchondral layer of the bony otic capsule. Patients present with bilateral conductive hearing loss and tinnitus. An otosclerotic patient hears better in noisy than in quiet surroundings. This is called Paracusis willi. The tympanic membrane is quite normal and mobile. Sometimes, a reddish hue may be seen on the promontory through the tympanic membrane (Schwartze sign). This is indicative of active focus with increased vascularity. On Pure tone audiometry, a characteristic dip in bone conduction curve max at 2000 Hz is seen. This is called cahart’s notch.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. TORP is used in tympanoplasty procedures. Option B. Grommet insertion is done in otitis media with effusion if myringotomy and aspiration combined with medical measures have not helped and fluid recurs. Option C. PORP is also used in tympanoplasty procedures.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Following instrument is used for the examination of the ear. All the following structures can be seen by it except:", "options": [{"label": "A", "text": "Round Window", "correct": true}, {"label": "B", "text": "Tympanic Membrane", "correct": false}, {"label": "C", "text": "External Auditory Canal", "correct": false}, {"label": "D", "text": "All the above can be seen", "correct": false}], "correct_answer": "A. Round Window", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004559773-QTDE053008IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Round Window The device shown above is an otoscope, and structures up to the tympanic membrane can be viewed. Since the round window lies medial to the tympanic membrane, hence cannot be seen by the otoscope.</p>\n<p><strong>Highyeild:</strong></p><p>OTOSCOPE It is an electric or battery-operated device with a magnifying glass. Sometimes it has the arrangement to attach a bulb to function as Siegel’s speculum. It is helpful for a detailed examination of the ear. It is an essential instrument to examine the ear of an infant, a child or a bedridden patient.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "During the practical viva examination, a student is asked to pick up the following instrument. The student identifies the instrument as a head mirror. What type of mirror is used, and its focal length?", "options": [{"label": "A", "text": "Concave mirror, 25 cm", "correct": true}, {"label": "B", "text": "Convex mirror, 30 cm", "correct": false}, {"label": "C", "text": "Concave mirror, 15 cm", "correct": false}, {"label": "D", "text": "Convex mirror, 15 cm", "correct": false}], "correct_answer": "A. Concave mirror, 25 cm", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004559984-QTDE053009IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Concave mirror, 25 cm The given image is of the head mirror . It is a concave mirror that reflects light from the Bull’s eye lamp onto the examined part. It has a focal length of approximately 25 cm .</p>\n<p><strong>Highyeild:</strong></p><p>The examiner sees through the hole in the centre of the mirror. The diameter of the mirror is 89 mm (31⁄2“), and that of the central hole is 19 mm (3/4”). Bull’s eye lamp - It provides a powerful source of light. The lamp can be tilted, rotated, raised or lowered according to the needs.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Choose the incorrect statement regarding the test done by the following device- Used to distinguish cochlear from retro cochlear hearing loss Screening test of choice for hearing in neonates Result is not affected by conditions of the middle ear Detects biological activity of inner hair cells.", "options": [{"label": "A", "text": "I, II", "correct": false}, {"label": "B", "text": "III & IV", "correct": true}, {"label": "C", "text": "IV Only", "correct": false}, {"label": "D", "text": "I & III", "correct": false}], "correct_answer": "B. III & IV", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004560324-QTDE053010IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>III & IV The image shown above is of Oto acoustic emission device. They are low-intensity sounds produced by the outer hair cells of a normal cochlea and can be elicited by a very sensitive microphone placed in the external ear canal and analysed by a computer. So they detect the biological activity of outer hair cells and not inner hair cells. So statement IV is false. And results are affected in middle ear disease. So statement III is false.</p>\n<p><strong>Highyeild:</strong></p><p>Otoacoustic Emission They are low-intensity sounds produced by the outer hair cells of a normal cochlea. They can be elicited by a very sensitive microphone placed in the external ear canal and analysed by a computer. The sound produced by outer hair cells travels in a reverse direction: outer hair cells →basi- lar membrane → perilymph → oval window → ossicles → tympanic membrane → ear canal. OAEs are present when outer hair cells are healthy and are absent when they are damaged and thus help to test the function of the cochlea. Uses OAEs are used as a screening test for hearing in neonates and to test hearing in uncooperative or mentally challenged individuals after sedation. Sedation does not interfere with OAEs. They help to distinguish cochlear from retrocochlear hearing loss. OAEs are absent in cochlear lesions, e.g. ototoxic sensorineural hearing loss. They detect ototoxic effects earlier than pure tone audiometry. OAEs are also helpful in diagnosing retrocochlear pathology, especially auditory neuropathy. OAEs are absent in 50% of normal individuals, lesions of the cochlea, middle ear disorders (as sound travelling in the reverse direction cannot be picked up) and when a hearing loss exceeds 30 dB.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All the following are contraindications of the use of the following instrument except-", "options": [{"label": "A", "text": "Button battery impacted in ext ear", "correct": false}, {"label": "B", "text": "Perforation of tympanic membrane", "correct": false}, {"label": "C", "text": "Acute Otitis Externa", "correct": false}, {"label": "D", "text": "Inorganic foreign bodies in the ear (beads, pellets)", "correct": true}], "correct_answer": "D. Inorganic foreign bodies in the ear (beads, pellets)", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004560782-QTDE053011IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Inorganic foreign bodies in the ear (beads, pellets) The image shows an aural syringe used for syringing the ear . It removes wax, cerumen, and foreign bodies in the external ear. Inorganic foreign bodies in the ear are not a contraindication of syringing.</p>\n<p><strong>Highyeild:</strong></p><p>Contraindications of syringing: The present of button batteries which may leak on exposure to water Acute inflammatory conditions of the external ear Perforatpresenceed TM Organic foreign bodies which may absorb water and swell (e.g. beans) History of ear surgery Unnecessary syringing should be avoided. At the end of the procedure, the ear canal and tympanic membrane must be inspected and dried with a pledget of cotton.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Present of button batteries that may leak on exposure to water is a contraindication for syringing. Option B. Perforated Tympanic membrane is also a contraindication for syringing. Option C. Acute inflammatory conditions of the external ear are also a contraindication for syringing.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Instrument shown below is used in which audiometric tests?", "options": [{"label": "A", "text": "Gille’s Test", "correct": true}, {"label": "B", "text": "Bing Test", "correct": false}, {"label": "C", "text": "Schwabach Test", "correct": false}, {"label": "D", "text": "Rinne Test", "correct": false}], "correct_answer": "A. Gille’s Test", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004561496-QTDE053012IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Gille’s Test The instrument shown in the image is the Siegel speculum. It may be used in Gille’s test.</p>\n<p><strong>Highyeild:</strong></p><p>Gelle’s test . It is also a test of bone conduction and examines the effect of increased air pressure in the ear canal on hearing. Usually, when air pressure is increased in the ear canal by Siegel’s speculum, it pushes the tympanic membrane and ossicles inwards, raises the intralabyrinthine pressure and causes immobility of the basilar membrane and decreased hearing. Still, no change in hearing is observed when the ossicular chain is fixed or disconnected.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B . Bing test is a test of bone conduction and examines the effect of occlusion of the ear canal on hearing. A vibrating tuning fork is placed on the mastoid while the examiner alternately closes and opens the ear canal by pressing the tragus inwards. A normal person or one with sensorineural hearing loss hears louder when the ear canal is occluded and softer when the canal is open (Bing positive). A patient with conductive hearing loss will appreciate no change (Bing negative). Option C. In Schwabach’s test BC of the patient is compared with that of the normal hearing person (examiner), but the meatus is not occluded. It has the same significance as the absolute bone conduction test. Schwabach is reduced in sensorineural deafness and lengthened in conductive deafness. Option D . In the Rinne test, air conduction of the ear is compared with its bone conduction. A vibrating tuning fork is placed on the patient’s mastoid, and when he stops hearing, it is brought beside the meatus. If he still hears, AC is more than BC. Rinne's test is positive when AC is longer or louder than BC. It is seen in normal persons or those having sensorineural deafness. A negative Rinne (BC > AC) is seen in conductive deafness.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A pregnant lady visits ENT OPD with complaints of hearing loss. The resident doctor uses the following device to evaluate, and identify the image:", "options": [{"label": "A", "text": "Bera", "correct": false}, {"label": "B", "text": "Tympanometer", "correct": true}, {"label": "C", "text": "Speech Audiometer", "correct": false}, {"label": "D", "text": "Digital Audiometer.", "correct": false}], "correct_answer": "B. Tympanometer", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004561858-QTDE053013IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tympanometer The given image is of a tympanometer. It is used to evaluate the middle ear.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. BERA test has multiple electrodes shown. Option C. Speech audiometry. Option D. Digital audiometry.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 22 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 3</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Instruments for Nose & Throat - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 3</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 3 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Identify the following instrument.", "options": [{"label": "A", "text": "Macewen's Curette", "correct": false}, {"label": "B", "text": "Lichtwitz Trocar", "correct": true}, {"label": "C", "text": "Farabeuf Periosteal Elevator", "correct": false}, {"label": "D", "text": "Mastoid Gouge", "correct": false}], "correct_answer": "B. Lichtwitz Trocar", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004624319-QTDE054001IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lichtwitz Trocar Above image is of Lichtwitz trocar and cannula .</p>\n<p><strong>Highyeild:</strong></p><p>Lichtwitz trocar and cannula . Used for proof of puncture (antral lavage). Puncture is done in the inferior meatus as this site is easily accessible and safe.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Macewen's Curette Option C. Farabeuf Periosteal Elevator Option D. Mastoid Gouge</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "What is the name of the instrument?", "options": [{"label": "A", "text": "Tracheostomy Tube", "correct": false}, {"label": "B", "text": "Montgomery T Tube", "correct": true}, {"label": "C", "text": "Blom Singer Prosthesis", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "B. Montgomery T Tube", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004624428-QTDE054003IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Montgomery T Tube The above image is of the Montgomery T tube.</p>\n<p><strong>Highyeild:</strong></p><p>Montgomery T tube The Montgomery T-tube is a silicone stent with a long central lumen and a smaller lumen projecting from the side of the stent at either a 90 or 75 angle. It is used to stent the larynx and trachea after reconstruction for areas of malacia and stenosis. It is a soft stent and can be left in place for periods over 12 months. Its main advantage is that the patient can speak. It is prone to crusting, and to counteract this, it is best to plug the side lumen as much as possible and intermittently perform proper suctioning to prevent blockage. It is not used in children under two years of age as the small size of the stent required has too narrow an internal diameter and can easily become blocked.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Tracheostomy Tube Option C. Blom Singer Prosthesis</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All of the following statements regarding this instrument are true except:", "options": [{"label": "A", "text": "Diameter of the central hole is 19mm", "correct": false}, {"label": "B", "text": "Diameter of the mirror is 89 mm", "correct": false}, {"label": "C", "text": "Focal length is 250mm", "correct": false}, {"label": "D", "text": "Mirror used is convex.", "correct": true}], "correct_answer": "D. Mirror used is convex.", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004624852-QTDE054004IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mirror used is convex. The above image is of the head mirror . It has a concave mirror, not a convex mirror.</p>\n<p><strong>Highyeild:</strong></p><p>HEAD MIRROR It is a concave mirror that reflects light from the Bull’s eye lamp onto the examined part. It has a focal length of approximately 25 cm. The examiner sees through the hole in the centre of the mirror. The diameter of the mirror is 89 mm (31⁄2“), and that of the central hole is 19 mm (3/4”). Illumination sources include electric headlights connected to a main power source through a step-down transformer or chargeable batteries.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Diameter of the central hole is 19mm is a true statement. O ption B. Diameter of the mirror is 89 mm is also a true statement. O ption C. Focal length is 250mm is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 13 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 3</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Intracranial Complications - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 3</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 3 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Infection of CNS spread in the inner ear through:", "options": [{"label": "A", "text": "Cochlear Aqueduct", "correct": true}, {"label": "B", "text": "Endolymphatic SAC", "correct": false}, {"label": "C", "text": "Vestibular Aqueduct", "correct": false}, {"label": "D", "text": "Hyrtl Fissure", "correct": false}], "correct_answer": "A. Cochlear Aqueduct", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cochlear Aqueduct As we know, the cochlear aqueduct (Aqueduct of Cochlea) is a connection between scala tympani (containing perilymph) and the subarachnoid space (containing CSF). On occasions, particularly in young children, the Cochlear aqueduct is large and open. Infection can spread to the inner ear from the infected CSF or vice versa, via the cochlear aqueduct resulting in severe profound hearing loss (meningitic labyrinthitis).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 15-year-old male has nominal aphasia. There is also a history of scanty, foul-smelling discharge in the past. The patient reports some bleeding while cleaning the ear. Which of the following is the most likely diagnosis?", "options": [{"label": "A", "text": "Extradural Abscess", "correct": false}, {"label": "B", "text": "Temporal Lobe Abscess", "correct": true}, {"label": "C", "text": "Cavernous Thrombosis", "correct": false}, {"label": "D", "text": "Lateral Sinus Thrombophlebitis", "correct": false}], "correct_answer": "B. Temporal Lobe Abscess", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Temporal Lobe Abscess A history of scanty, foul-smelling discharge says us that he had Otitis media. Now he has nominal aphasia which occurs in temporal lobe abscess which is an intracranial complication of Otitis media.</p>\n<p><strong>Highyeild:</strong></p><p>Nominal aphasi- If the abscess involves the dominant hemisphere, i.e. left hemisphere in right-handed persons, the patient fails to tell the names of common objects such as keys, pens, etc. but can demonstrate their use. Homonymous hemianopia - This is due to pressure on the optic radiations. The visual field, opposite to the side of the lesion, is lost. Contralateral motor paralysi- In the usual upward spread of abscess, the face is involved first followed by the arm and leg. Inward spread, towards the internal capsule, involves the leg first followed by the arm and the face. Epileptic fits - Involvement of uncinate gyrus causes hallucinations of taste, and smell and involuntary smacking movements of lips and tongue. Generalized fits may occur.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The commonest intracranial complication of CSOM is:", "options": [{"label": "A", "text": "Conductive Deafness", "correct": false}, {"label": "B", "text": "Meningitis", "correct": true}, {"label": "C", "text": "Temporal Lobe Abscess", "correct": false}, {"label": "D", "text": "Cholesteatoma", "correct": false}], "correct_answer": "B. Meningitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Meningitis Meningitis is the most common intracranial complication of ear disease (CSOM).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 13 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 4</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Introduction & Examination of ENT - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 4</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 4 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "10-year-old girl child presented with complaints of discharge and pain in her left ear. On examination with the bull's eye lamp and head mirror, there was a foreign body in the same ear. What is the approximate focal length of the head mirror?", "options": [{"label": "A", "text": "25 cm", "correct": true}, {"label": "B", "text": "89 mm", "correct": false}, {"label": "C", "text": "19 mm", "correct": false}, {"label": "D", "text": "6-30 mm", "correct": false}], "correct_answer": "A. 25 cm", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>25 cm The focal length of the head mirro r is approximately 25 cm.</p>\n<p><strong>Highyeild:</strong></p><p>The head mirror is a concave mirror used to reflect light from the Bull’s eye lamp onto the part being examined. It has a focal length of approximately 25 cm. The examiner sees through the hole in the centre of the mirror. The diameter of the mirror is 89 mm (3½”) and that of the central hole is 19 mm (3/4”).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 65-year-old female patient presented with complaints of transient vertigo often induced during cleaning the ear. She had a history of chronic suppurative otitis media with cholesteatoma. Which of the following equipment is used for examining the tympanic membrane as well as for eliciting the fistula test?", "options": [{"label": "A", "text": "Siegle’S Speculum", "correct": true}, {"label": "B", "text": "Tuning Fork", "correct": false}, {"label": "C", "text": "Jobson Horne’S Probe", "correct": false}, {"label": "D", "text": "Ear Specula", "correct": false}], "correct_answer": "A. Siegle’S Speculum", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Siegle’S Speculum Siegle’s speculum is Essential in the examination of the tympanic membrane ; it gives a magnified view of the tympanic membrane and helps to test its mobility . It is also used to elicit the fistula sign.</p>\n<p><strong>Highyeild:</strong></p><p>SIEGLE’S SPECULUM Essential in the examination of the tympanic membrane. It gives a magnified view of the tympanic membrane and helps to test its mobility. It is also used to elicit the fistula sign. Use of Siegel's speculum to see the mobility of the tympanic membrane.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. Tuning forks Commonly used tuning fork has a frequency of 512 Hz. Forks of other frequencies, e. g. 256 and 1024 Hz should also be available. For eliciting the Rinnes test and Webers test. Option C. Jobson-Horne’s probe: One end of the probe is used to form a cotton bud to clean the ear of discharge and the other end (with ring curette) is used to remove the wax. Option D. Ear specula: Various sizes are available to suit different sizes of the ear canal. The largest speculum which can be conveniently inserted in the ear canal should be used.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "18-year-old male presented with chief complaints of nasal obstruction which was unilateral (left) and epistaxis. As a part of the examination patient sits facing the examiner and opens his mouth and examiner depressed the tongue using a depressor and introduced a mirror. Name the procedure done.", "options": [{"label": "A", "text": "Examination of the external nose", "correct": false}, {"label": "B", "text": "Examination of Vestibule", "correct": false}, {"label": "C", "text": "Anterior Rhinoscopy", "correct": false}, {"label": "D", "text": "Posterior Rhinoscopy", "correct": true}], "correct_answer": "D. Posterior Rhinoscopy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Posterior Rhinoscopy The procedure described above is of posterior rhinoscopy.</p>\n<p><strong>Highyeild:</strong></p><p>POSTERIOR RHINOSCOPY The patient sits facing the examiner, opens his mouth and breathes quietly from the mouth. The examiner depresses the tongue with a tongue depressor and introduces a posterior rhinoscopic mirror, which has been warmed and tested on the back of the hand. The mirror is held like a pen and carried behind the soft palate. Without touching it on the posterior third of the tongue to avoid a gag reflex, light from the head mirror is focussed on the rhinoscopic mirror which further illuminates the part to be examined. The patient’s relaxation is important so that the soft palate does not contract. Technique of posterior rhinoscopy.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Examine the skin and osteocartilaginous framework of the nose both by inspection and palpation. Skin is examined for signs of inflammation (furuncle, septal abscess), scars (operation or trauma), sinus (congenital dermoid), swelling (dermoid or glioma) or a neoplasm (basal cell or squamous cell carcinoma). The osteocartilaginous framework is examined for deformity, e. g. deviated or twisted nose, hump, depressed bridge, bifid or pointed tip, and destruction of the nose (trauma, syphilis, cancer). O ption B. Vestibule -It is the anterior skin-lined part of the nasal cavity having vibrissae and can be easily examined by tilting the tip of the nose upwards. It is examined for a furuncle, a fissure, crusting, dislocated caudal end of septum and tumours Option C. Anterior rhinoscopy in which the patient is seated facing the examiner. A thudicum or Vienna type of speculum is used to open the vestibule. The speculum is held in the left hand. It should be fully closed while introducing and partially open when removing from the nose to avoid catching hair.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 68-year-old female was diagnosed with follicular carcinoma of the thyroid. Surgery with conserving spinal accessory nerve, internal jugular vein and sternocleidomastoid is suggested for her. Name this type of neck dissection.", "options": [{"label": "A", "text": "Radical Neck Dissection", "correct": false}, {"label": "B", "text": "Modified Neck Dissection", "correct": true}, {"label": "C", "text": "Selective Neck Dissection", "correct": false}, {"label": "D", "text": "Extended Neck Dissection", "correct": false}], "correct_answer": "B. Modified Neck Dissection", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Modified Neck Dissection Modified neck dissection is similar to radical neck dissection but with the preservation of one or more of the following structures: Spinal accessory nerve Internal jugular vein Sternocleidomastoid muscle</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A . Radical neck dissection is En bloc clearance of fibro-fatty tissue from one side of the neck, including the lymph nodes from levels I - V and lymph node that surround the tail of the parotid gland, with the removal of 3 important structures: The spinal accessory nerve, The internal jugular, The Sternocleidomastoid muscle. O ption C. In Selective Neck Dissection Don’t remove all five groups of LN; Selectively preserve some groups of LN. Option D. Extended neck dissection consists of any of the neck dissections as described and further extended to include additional lymph node groups or nonlymphatic structures or both. Additional lymph node groups include retropharyngeal, parotid or level VI nodes and nonlymphatic structures may include external carotid artery, hypoglossal nerve, parotid gland and levator scapulae, etc. that are not routinely included in that dissection.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 14 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 11</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Juvenile Nasopharyngeal Angiofibroma - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 11</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 11 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Nasal Angiofibroma is seen at which age:", "options": [{"label": "A", "text": "Adolescent Male", "correct": true}, {"label": "B", "text": "Adult Male", "correct": false}, {"label": "C", "text": "Elderly Male", "correct": false}, {"label": "D", "text": "Elderly Female", "correct": false}], "correct_answer": "A. Adolescent Male", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Adolescent Male Juvenile nasopharyngeal angiofibroma is predominantly seen in adolescent males in the second decade of life . It is thought to be testosterone dependent.</p>\n<p><strong>Highyeild:</strong></p><p>JUVENILE NASOPHARYNGEAL ANGIOFIBROMA MC site - Sphenopalatine foramen It is non–capsulated, Benign fast, growing (Aggressive) and vascular tumour of the nasopharynx that is only seen in Juvenile males Juvenile – 10-16yrs (Never seen in females, adults) Presentation A juvenile male with severe and recurrent epistaxis (artery supplying tumorMaxillary artery/Sphenopalatine artery) Cheek swelling Hearing loss due to glue ear Any nasal symptoms Cranial symptoms Proptosis</p>\n<p><strong>Extraedge:</strong></p><p>Antral sign/Holman – Miller sign: Anterior bowing of posterior maxillary wall. Hondusa sign: Increase distance between maxilla and mandible cheek swelling +. Frog face deformity: Tumor is in orbit and Ethmoid.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 15 year aged boy presents with unilateral nasal blockade mass in the cheek and recurrent epistaxis; the likely diagnosis is:", "options": [{"label": "A", "text": "Nasopharyngeal Ca", "correct": false}, {"label": "B", "text": "Angiofibroma", "correct": true}, {"label": "C", "text": "Inverted Papilloma", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "B. Angiofibroma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Angiofibroma The question shows that he is an adolescent male with recurrent epistaxis and nasal blockade, which indicates angiofibroma as the diagnosis.</p>\n<p><strong>Highyeild:</strong></p><p>JUVENILE NASOPHARYNGEAL ANGIOFIBROMA MC site - Sphenopalatine foramen It is non – capsulated, Benign fast, growing (Aggressive) and vascular tumor of the Nasopharynx that is only seen in Juvenile males Juvenile – 10-16yrs (Never seen in females, adults) Presentation A juvenile male with severe and recurrent epistaxis (artery supplying tumorMaxillary artery/Sphenopalatine artery) Cheek swelling Hearing loss due to glue ear Any nasal symptoms Cranial symptoms Proptosis</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Nasopharyngeal carcinoma is mainly seen in the fifth to seventh decade. Option: C. In inverted papilloma, men are affected more than women aged 40–70. It is almost always unilateral and presents with nasal obstruction, nasal discharge and epistaxis.</p>\n<p><strong>Extraedge:</strong></p><p>Antral sign/Holman – Miller sign: Anterior bowing of posterior maxillary wall. Hondusa sign: Increase distance between maxilla and mandible cheek swelling +. Frog face deformity: Tumor is in orbit and Ethmoid.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The main vessel involved in bleeding from Juvenile nasopharyngeal angiofibroma:", "options": [{"label": "A", "text": "Facial Artery", "correct": false}, {"label": "B", "text": "Ascending Pharyngeal Artery", "correct": false}, {"label": "C", "text": "Internal Maxillary Artery", "correct": true}, {"label": "D", "text": "Anterior Ethmoidal Artery", "correct": false}], "correct_answer": "C. Internal Maxillary Artery", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Internal Maxillary Artery Juvenile nasopharyngeal angiofibroma is a locally invasive vasoformative tumor with endothelium-lined vessels without a muscle coat. The primary blood supply is from the internal maxillary artery.</p>\n<p><strong>Highyeild:</strong></p><p>JUVENILE NASOPHARYNGEAL ANGIOFIBROMA MC site - Sphenopalatine foramen It is non – capsulated, Benign fast, growing (Aggressive) and vascular tumor of the Nasopharynx, that is only seen in Juvenile males Juvenile – 10-16yrs (Never seen in females, adults) Presentation A juvenile male with severe and recurrent epistaxis (artery supplying tumor Maxillary artery/Sphenopalatine artery) Cheek swelling Hearing loss due to glue ear Any nasal symptoms Cranial symptoms Proptosis</p>\n<p><strong>Extraedge:</strong></p><p>Antral sign/Holman – Miller sign: Anterior bowing of posterior maxillary wall. Hondusa sign: Increase distance between maxilla and mandible cheek swelling +. Frog face deformity: Tumor is in orbit and Ethmoid.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 19-year-old male patient presented with complaints of unilateral(right) nasal obstruction, epistaxis, and continuing nasal discharge for one month, with a mass in his nasal cavity. He had intermittent headaches for which he is under medication, nasal bleeding episodes lasting for 10 minutes, and serous discharge for one month. There was no history of nasal bleeding before one month, but intermittent nasal discharge was present. Endoscopic examination of the nose reveals that the mass was sessile, lobulated with a smooth surface, filling the left inferior and middle meatus, extending back, and filling the choana. What would be the diagnosis?", "options": [{"label": "A", "text": "Enlarged Adenoids", "correct": false}, {"label": "B", "text": "Olfactory Neuroblastoma", "correct": false}, {"label": "C", "text": "Juvenile Nasopharyngeal Angiofibroma", "correct": true}, {"label": "D", "text": "Rhabdomyosarcoma", "correct": false}], "correct_answer": "C. Juvenile Nasopharyngeal Angiofibroma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Juvenile Nasopharyngeal Angiofibroma On endoscopic examination of juvenile nasopharyngeal angiofibroma, Tumour mass is usually seen in the nose and nasopharynx with blood-stained discharge . It is sessile, lobulated or smooth and obstructs one or both choanae. Tumour is seen almost exclusively in males in the age group of 10-20 years.</p>\n<p><strong>Highyeild:</strong></p><p>JUVENILE NASOPHARYNGEAL ANGIOFIBROMA MC site - Sphenopalatine foramen It is non – capsulated, Benign fast, growing (Aggressive) and vascular tumor of the Nasopharynx that is only seen in Juvenile males Juvenile – 10-16yrs (Never seen in females, adults) Presentation A juvenile male with severe and recurrent epistaxis (artery supplying tumorMaxillary artery/Sphenopalatine artery) Cheek swelling Hearing loss due to glue ear Any nasal symptoms Cranial symptoms Proptosis</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Enlarged adenoids are usually seen in children. Symptoms and signs depend not merely on the absolute size of the adenoid mass but are related to the available space in the nasopharynx. Nasal symptoms are nasal obstruction, discharge sinusitis, epistaxis and voice changes. Option: B. Esthesioneuroblastoma – malignant (Syn. Olfactory Neuroblastoma ) Also called olfactory placode tumour as it arises from the olfactory epithelium in the upper third of the Bimodal incidence peaks at 10-20 and 50-60 years are seen. The most common symptoms are unilateral nasal obstruction and epistaxis. Option: D. Rhabdomyosarcoma is the most common primary malignant tumour of orbit in children and is usually seen at 6-7 years of age. It can occur even in the newborn. It is painless but progressive proptosis and can spread to the adjoining paranasal sinuses.</p>\n<p><strong>Extraedge:</strong></p><p>Antral sign/Holman – Miller sign: Anterior bowing of posterior maxillary wall. Hondusa sign: Increase distance between maxilla and mandible cheek swelling +. Frog face deformity: Tumor is in orbit and Ethmoid.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "16-year-old male patient, diagnosed with a JNA measuring 2 × 3 × 5 cm. He had symptoms like progressive unilateral nasal obstruction, nighttime snoring, headache, daytime oral breathing, and mild epistaxis. On physical examination with anterior rhinoscopy, it reveals a reddish lobed mass located in the back of the nasal cavity. Which among the following investigation of choice is now used to detect the extent of tumor and bone destruction?", "options": [{"label": "A", "text": "Contrast-enhanced CT scan of the head", "correct": true}, {"label": "B", "text": "MRI", "correct": false}, {"label": "C", "text": "Digital Subtraction Angiography", "correct": false}, {"label": "D", "text": "Grouping and matching the blood.", "correct": false}], "correct_answer": "A. Contrast-enhanced CT scan of the head", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Contrast-enhanced CT scan of the head Contrast-enhanced CT scan of the head is now the investigation of choice . It has replaced all conventional radiographs . CT shows the extent of the tumour, bony destruction or displacements. Anterior bowing of the maxillary sinus's posterior wall often called an antral sign or Holman Miller sign, is pathognomic of angiofibroma.</p>\n<p><strong>Highyeild:</strong></p><p>Signs seen in juvenile nasopharyngeal angiofibroma Antral sign/Holman – Miller sign : Anterior bowing of posterior maxillary wall. Hondusa sign : Increase distance between maxilla and mandible cheek swelling +. Frog face deformity : Tumor is in orbit and Ethmoid.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Magnetic resonance imaging (MRI) is complementary to CT scans and shows any soft tissue extensions present intracranially into the middle or anterior cranial fossa, cavernous sinus, optic chiasma region the temporal/infratemporal fossa or into the orbit, It also differentiates whether the tumour is extradural or intradural. Option: C. Digital subtraction angiography shows the extent of the tumour; it is vascularity and feeding vessels which mostly come from. In very large tumours or those with intracranial extension, blood supply may also come from the internal carotid system. Option: D. Grouping and matching the blood: Though blood may not be required during surgery if successful embolisation has been done, 2-3 units of blood or packed RBC should be available and kept in reserve after grouping and cross-matching.</p>\n<p><strong>Extraedge:</strong></p><p>Nasopharyngeal fibroma is a benign tumour but locally invasive and destroys the adjoining structures. It may ex- tend into: Nasal cavity causing nasal obstruction, epistaxis and nasal discharge. Paranasal sinuses. Maxillary, sphenoid and ethmoid sinuses can all be invaded. Pterygomaxillary fossa, infratemporal fossa and cheek.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 15-year-old male patient with complaints of flu-like illness, he perceived a predominantly right nasal obstruction, with greater intensity at night, nasal discharge, sometimes accompanied by mild epistaxis and nighttime snoring. Using an endoscope support, there was a pinkish-pearl right nasal tumor with a smooth, vascularised, ovoid surface that hung from the back of the middle meatus and occupies the choana. What would be your treatment of choice?", "options": [{"label": "A", "text": "Surgical Excision", "correct": true}, {"label": "B", "text": "Radiotherapy", "correct": false}, {"label": "C", "text": "Chemotherapy", "correct": false}, {"label": "D", "text": "Hormonal Therapy", "correct": false}], "correct_answer": "A. Surgical Excision", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Surgical Excision From the above history and examination findings, a diagnosis of juvenile nasopharyngeal angiofibroma is made. Surgical excision is the treatment of choice though radiotherapy and chemotherapy singly or combined have also been used. As previously thought, spontaneous regression of the tumour with the advancement of age does not occur, and no wait-and-watch policy should be adopted. Surgical approaches are used to remove angiofibroma, depending on its origin and extensions.</p>\n<p><strong>Highyeild:</strong></p><p>JUVENILE NASOPHARYNGEAL ANGIOFIBROMA MC site - Sphenopalatine foramen Is non – capsulated, Benign fast growing (Aggressive) and vascular tumor of Nasopharynx, that is only seen in Juvenile males Juvenile – 10-16yrs (Never seen in females, adults) Presentation A juvenile male with severe and recurrent epistaxis (artery supplying tumorMaxillary artery/Sphenopalatine artery) Cheek swelling Hearing loss due to glue ear Any nasal symptoms Cranial symptoms Proptosis</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Radiotherapy has been used as a primary stand-alone mode of treatment, thus avoiding surgery. A dose of 3000 to 3500 cGy in 15-18 fractions is delivered in 3-3.5 weeks, Response is not immediate. Tumour regresses slowly in about a year, sometimes up to 3 years. Radiotherapy is also used in intracranial extension of disease when the tumour derives its blood supply from the internal carotid system. Treatment with radiotherapy is controversial. Option: C. Chemotherapy for very aggressive recurrent tumours and residual lesions has been treated chemotherapy. Doxorubicin, vincristine and dacarbazine have been used in combination. Option: D. Since the tumour occurs in young males at puberty, probably activated by testosterone, hormonal therapy as the primary or adjunctive treatment has been used. Diethylstilbestrol and flutamide (an androgen blocker) have been used to arrest the growth of the tumour, but no significant regression has been observed in practice.</p>\n<p><strong>Extraedge:</strong></p><p>Antral sign/Holman – Miller sign: Anterior bowing of posterior maxillary wall. Hondusa sign: Increase distance between maxilla and mandible cheek swelling +. Frog face deformity: Tumor is in orbit and Ethmoid.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 10-year-old boy who came to the Department of Otolaryngology, complaining about the fullness of bilateral ears associated with decreased hearing for six months and painless nasal blockage for three months. On nasal endoscopy, a proliferative mass obstructing the right nasal cavity was noted. It was firm in consistency and bled on touch. Why does the tumor mass bleed on touch?", "options": [{"label": "A", "text": "Increased amount of collagen", "correct": false}, {"label": "B", "text": "Lack of elastic tissue or muscle fibres", "correct": true}, {"label": "C", "text": "Presence of Capsule", "correct": false}, {"label": "D", "text": "Due to the nuclei of Stromal cells", "correct": false}], "correct_answer": "B. Lack of elastic tissue or muscle fibres", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lack of elastic tissue or muscle fibres From the above history and examination findings, diagnosis of juvenile nasopharyngeal angiofibroma is made. Lack of elastic tissue or muscle fibres in the vessels is responsible for profuse bleeding on manipulation or biopsy of the tumour as the vessels do not contract. The thin-walled capillaries are seen at the periphery of the tumour , while hyperplastic vessels with incomplete muscle coats may be seen in the centre of the tumour.</p>\n<p><strong>Highyeild:</strong></p><p>JUVENILE NASOPHARYNGEAL ANGIOFIBROMA MC site - Sphenopalatine foramen Is non – capsulated, Benign fast growing (Aggressive) and vascular tumor of Nasopharynx, that is only seen in Juvenile males Juvenile – 10-16yrs (Never seen in females, adults) Presentation A juvenile male with severe and recurrent epistaxis (artery supplying tumorMaxillary artery/Sphenopalatine artery) Cheek swelling Hearing loss due to glue ear Any nasal symptoms Cranial symptoms Proptosis</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. It is the amount of collagen tissue that decides whether the tumour is soft or firm. Option: C. The option itself is wrong. JNA is a benign condition, and the tumor mass has no capsule Option: D. Nuclei of Stromal cells show reactivity to beta-catenin, indicating a role in the proliferation and growth of tumours.</p>\n<p><strong>Extraedge:</strong></p><p>Antral sign/Holman – Miller sign: Anterior bowing of posterior maxillary wall. Hondusa sign: Increase distance between maxilla and mandible cheek swelling +. Frog face deformity: Tumor is in orbit and Ethmoid.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 10-year-old boy presented with left-sided nasal obstruction and epistaxis. An endoscopic evaluation revealed a polypoid mass in the vestibule arising from the lateral wall of the nasal cavity. After the endoscopic and histopathological analyses confirmed the diagnosis as Nasopharyngeal fibroma. What is the reason behind the growth of this tumor in adolescent males?", "options": [{"label": "A", "text": "Genetic", "correct": false}, {"label": "B", "text": "Testosterone Dependent", "correct": true}, {"label": "C", "text": "Due to more malformed blood vessels in male", "correct": false}, {"label": "D", "text": "Lack of Immunity", "correct": false}], "correct_answer": "B. Testosterone Dependent", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Testosterone Dependent The exact cause is unknown. As the tumour is predominantly seen in adolescent males in the second decade of life, it is thought to be testosterone dependent . Such patients have a hamartomatous nidus of vascular tissue in the nasopharynx, and this is activated to form angiofibroma when the male sex hormone appears. The nidus of fibrovascular tissue is due to incomplete regression of the first branchial artery leaving behind a plexus of the vessel.</p>\n<p><strong>Highyeild:</strong></p><p>Signs seen in juvenile nasopharyngeal angiofibroma Antral sign/Holman – Miller sign : Anterior bowing of posterior maxillary wall. Hondusa sign : Increase distance between maxilla and mandible cheek swelling +. Frog face deformity : Tumor is in orbit and Ethmoid.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Genetic cause is not a significant cause for the growth of Juvenile nasopharyngeal angiofibroma. Option: C. malformed blood vessels are also not a significant cause for the growth of Juvenile nasopharyngeal angiofibroma. Option: D. lack of immunity is also not a significant cause for the growth of Juvenile nasopharyngeal angiofibroma.</p>\n<p><strong>Extraedge:</strong></p><p>SIGNS SEEN IN JUVENILE NASOPHARYNGEL ANGIOFIBROMA</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An elderly male came to ENT OPD with complaints of voice change. He is a chronic smoker. On examination, growth is visible, limited to one subsite of supraglottis with normal vocal cord mobility. No regional lymph node enlargement. No distant METs seen. What is the TNM staging?", "options": [{"label": "A", "text": "T1N0M0", "correct": true}, {"label": "B", "text": "T2N0M0", "correct": false}, {"label": "C", "text": "T1N1M0", "correct": false}, {"label": "D", "text": "T2N1M0", "correct": false}], "correct_answer": "A. T1N0M0", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>T1N0M0 The tumour is limited to one subsite of supraglottis with normal vocal cord mobility . No regional lymph node enlargement . No distant metastasis is seen. So staging of the tumour will be T1N0M0.</p>\n<p><strong>Highyeild:</strong></p><p>TNM CLASSIFICATION OF CARCINOMA LARYNX TNM CLASSIFICATION OF CANCER LARYNX (AMERICAN JOINT COMMITTEE ON CANCER, 2002) Supraglottis T₁ Tumour limited to one subsite of supraglottis with normal vocal cord mobility. Τ 2 Tumour invades mucosa of more than one adjacent subsites of supraglottis or glottis or region outside the supraglottis (e.g., mucosa of base of tongue, vallecula, medial wall of pyriform sinus) without fixation of the larynx. T 3 Tumour limited to larynx with vocal cord fixation and/or invades any of the following: postcricoid area, pre-epiglottic tissues, paraglottic space and/or minor thyroid cartilage invasion. T 4 a Tumour invades through the thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscle of tongue, strap muscles, thyroid or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures. Glottis T₁ Tumour limited to vocal cord(s) (may involve anterior or posterior commissures) with normal mobility. T₁a Tumour limited to one vocal cord. T₁b Tumour involves both vocal cords. Τ 2 Tumour extends to supraglottis and/or subglottis, and/or with impaired vocal cord mobility. T 3 Tumour limited to the larynx with vocal cord fixation and/or invades paraglottic space and/or minor thyroid cartilage erosion. T 4 a Tumour invades through thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscles of the tongue, strap muscles, thyroid, or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures. Subglottis T 1 Tumour limited to the subglottis. T 2 Tumour extends to vocal cord(s) with normal or impaired mobility. T 3 Tumour limited to larynx with vocal cord fixation. T 4 a Tumour invades cricoid or thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscle of tongue, strap muscles, thyroid or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures. Regional lymph nodes (N) N X Regional lymph nodes cannot be assessed. N 0 No regional lymph node metastasis. N 1 Metastasis in a single ipsilateral lymph node, 3 cm or less in greatest dimension. N 2 Metastasis in a single ipsilateral lymph node, more than 3 cm but not more than 6 cm in greatest dimension, or multiple ipsilateral lymph nodes, none more than 6 cm in greatest dimension, or bilateral or contralateral lymph nodes, none more than 6 cm in greatest dimension. N 2 a Metastasis in a single ipsilateral lymph node more than 3 cm but not more than 6 cm in greatest dimension. N 2 b Metastasis in multiple ipsilateral lymph nodes, none more than 6 cm in greatest dimension N 2 c Metastasis in bilateral or contralateral lymph nodes, none more than 6 cm in greatest dimension N 3 Metastasis in a lymph node more than 6 cm in greatest dimension. Distant metastasis (M) M X Distant metastasis cannot be assessed.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "17-year-old male patient reported to the hospital with the chief complaint of swelling on the right side of the cheek since 1 year that had rapidly increased in size since last 3 months. It was associated with occasional intermittent pain, which aggravated on bending. The patient had frequent epistaxis from the right nostril with a feeling of the stuffiness of the nose and nasal discharge. There was a history of extraction of the upper right second molar after the appearance of swelling. The extra-oral examination suggested a lobular swelling on the right side of the face extending from a pterygomaxillary fissure and spreading into the infratemporal and temporal fossa and appearing in the cheek. He was Diagnosed as a case of Nasopharyngeal angiofibroma. What would be the direction of spread of the tumour that appeared in the cheek in this condition?", "options": [{"label": "A", "text": "Anterior", "correct": false}, {"label": "B", "text": "Posterior", "correct": false}, {"label": "C", "text": "Superior", "correct": false}, {"label": "D", "text": "Lateral", "correct": true}], "correct_answer": "D. Lateral", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lateral Juvenile nasopharyngeal angiofibroma grows laterally into the pterygopalatine fossa . Once in the pterygopalatine fossa, it pushes and later destroys the posterior wall of the maxillary sinus, comes out of the pterygomaxillary fissure and spreads into the infratemporal and temporal fossae, and appears in the cheek.</p>\n<p><strong>Highyeild:</strong></p><p>Extensions of Nasopharyngeal AngioFibroma Nasal cavity-causing nasal obstruction, epistaxis and nasal discharge. Paranasal sinuses. Maxillary, sphenoid and ethmoid sinuses can all be invaded. Pterygomaxillary fossa, infratemporal fossa and cheek. Orbits giving rise to proptosis and “frog-face deformity.” It enters through the inferior orbital fissure and destroys the orbit's apex. It can also enter the orbit through superior orbital fissures. Cranial cavity . It can extend into: (a) Anterior cranial fossa through the roof of ethmoids or cribriform plate. (b) Middle cranial fossa through erosion of the floor of the middle cranial fossa or indirectly by invading the sphenoid sinus and sella turcica. In the former case, the tumour lies lateral to the internal carotid artery and in the latter case, medial to the artery.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Tumour spreads anteriorly into the nasal cavity blocking the choana. Option: B. Posteriorly into the nasopharynx causing nasal obstruction and epistaxis, common early symptoms. Option: C. It extends superiorly into the sphenoid sinus by eroding its floor</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "17-year-old male patient is diagnosed with a JNA measuring 2 × 4 × 5 cm. He has symptoms like progressive unilateral nasal obstruction, night snoring, headache, daytime oral breathing, and mild epistaxis. On the CT scan, there was anterior bowing of the posterior wall of the maxillary sinus. It is called?", "options": [{"label": "A", "text": "Antral sign or Holman – Miller sign", "correct": true}, {"label": "B", "text": "Furstenberg Sign", "correct": false}, {"label": "C", "text": "Dodd’S Sign/Crescent Sign", "correct": false}, {"label": "D", "text": "Boyce Sign", "correct": false}], "correct_answer": "A. Antral sign or Holman – Miller sign", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Antral sign or Holman – Miller sign Holman-Miller sign (also called the antral sign) is seen in juvenile nasopharyngeal angiofibroma ; it refers to the anterior bowing of the posterior wall of the maxillary antrum.</p>\n<p><strong>Highyeild:</strong></p><p>Signs seen in juvenile nasopharyngeal angiofibroma Antral sign/Holman – Miller sign : Anterior bowing of posterior maxillary wall. Hondusa sign : Increase distance between maxilla and mandible cheek swelling +. Frog face deformity : Tumor is in orbit and Ethmoid.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Furstenberg sign is seen when the nasopharyngeal cyst is communicating intracranially, there is enlargement of the cyst on crying and upon compression of jugular vein. Option: C. DODD’S SIGN/CRESCENT SIGN – X-ray finding-Crescent of air between the mass and posterior pharyngeal wall. Positive in Antrochoanal polyp ; Negative in Angiofibroma Option: D. BOYCE SIGN – Laryngocoele-Gurgling sound on compression of external laryngocoele with reduction of swelling</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 21 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 12</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Laryngeal Carcinoma - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 12</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 12 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "High tracheostomy is indicated in:", "options": [{"label": "A", "text": "Ca Larynx", "correct": true}, {"label": "B", "text": "TB", "correct": false}, {"label": "C", "text": "Tetanus", "correct": false}, {"label": "D", "text": "Diphtheria", "correct": false}], "correct_answer": "A. Ca Larynx", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ca Larynx The only indication for high tracheostomy, is carcinoma of the larynx because, in such cases, the entire larynx would ultimately be removed and a fresh tracheostoma made in a clean area lower down.</p>\n<p><strong>Highyeild:</strong></p><p>TRACHEOSTOMY Tracheostomy has also been divided into high, mid or low. High tracheostomy It is done above the level of thyroid isthmus (isthmus lies against II, III and IV tracheal rings). It violates the first ring of the trachea. Tracheostomy at this site can cause perichondritis of the cricoid cartilage and subglot- tic stenosis and is always avoided. The only indication for high tracheostomy is carcinoma of the larynx because, in such cases, the entire larynx would ultimately be removed and a fresh tracheostome made in a clean area lower down. Mid tracheostomy It is the preferred one and is done through the II or III ring and would entail the division of the thyroid isthmus or its retraction upwards or downwards to expose this part of the trachea. Low tracheostomy It is done below the level of the isthmus. Trachea is deep at this level and close to several large vessels; also, there are difficulties with the tracheostomy tube, which impinges on the suprasternal notch. Skin incisions in tracheostomy. (A) Vertical midline incision. (B) Transverse incision.</p>\n<p><strong>Extraedge:</strong></p><p>Indications For Tracheostomy 1. Respiratory obstruction (a) Infections (i) Acute laryngo-tracheo-bronchitis, acute epiglottitis, diphtheria (ii) Ludwig's angina, peritonsillar, retropharyngeal or parapharyngeal abscess, tongue abscess (b) Trauma (i) External injury of larynx and trachea (ii) Trauma due to endoscopies, especially in infants and children (iii) Fractures of mandible or maxillofacial injuries Neoplasms (d) Foreign body larynx (e) Oedema larynx due to steam, irritant fumes or gases, allergy (angioneurotic or drug sensitivity), radiation (f) Bilateral abductor paralysis (g) Congenital anomalies - Laryngeal web, cysts, tracheo-oesophageal fistula - Bilateral choanal atresia 2. Retained secretions (a) Inability to cough (i) Coma of any cause, e.g. head injuries, cerebrovascular accidents, narcotic overdose (ii) Paralysis of respiratory muscles, e.g. spinal injuries, polio, Guillain-Barre syndrome, myasthenia gravis (iii) Spasm of respiratory muscles, tetanus, eclampsia, strychnine poisoning (b) Painful cough (c) Aspiration of pharyngeal secretions 3. Respiratory insufficiency Chronic lung conditions, viz. emphysema, chronic bronchitis, bronchiectasis, atelectasis Conditions listed in A and B</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A middle-aged man presented in ENT opd presented with a hoarseness breathy voice. On laryngoscopy, a vocal polyp can be seen. Which Laser can be used to remove polyps and other laryngeal surgeries?", "options": [{"label": "A", "text": "Argon", "correct": false}, {"label": "B", "text": "Co2", "correct": true}, {"label": "C", "text": "Diode laser", "correct": false}, {"label": "D", "text": "ND-YAG", "correct": false}], "correct_answer": "B. Co2", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Co2 CO2 laser does not penetrate deep into the tissues , decreasing the risk of scarring . KTP lasers and CO2 lasers are used in laryngeal surgeries.</p>\n<p><strong>Highyeild:</strong></p><p>Lasers In ENT Zones of tissue destruction caused by lasers. Various Types Of Lasers And Their Wavelength Type of laser Wavelength ● Argon 488-514 nm ● KTP (Potassium titanyl phosphate) 532 nm ● Nd:YAG (Neodymium: yttrium aluminium garnet) 1060 nm ● CO₂ (Carbon dioxide) 10,600 ● Ho: YAG (Holmium: YAG) 2100 nm ● Er:YAG (Erbium: YAG) 2960 nm ● Diode laser 600-1000 nm ● Tunable dye lasers 577 nm</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Argon laser is absorbed by haemoglobin and pigmented tissues, and thus it is used to treat port–wine strain, haemangioma and telangiectasias. When focused on a small point, it can vaporise the target tissue. Option: C. Diode lasers have been used in turbinate reduction, laser-assisted stapedectomy and mucosa-intact tonsillar ablation. Option: D. Nd Yag laser has been used to debulk tracheobronchial and oesophageal lesions for palliation, hereditary hemorrhagic telangiectasia and turbinectomy.</p>\n<p><strong>Extraedge:</strong></p><p>Precautions In The Use Of Lasers Display a sign outside OT “Lasers In Use.” Close the OT door. No entry or exit of staff permitted. Protective glasses, specific for the wavelength of the laser, Wet saline pads are placed on the eyes. Use a wavelength-specific endotracheal tube or wrap the tube with aluminium foil. Use noninflammable gases such as enflurane. Oxygen concentration in inhaled gases should not exceed 40%. Do not use N2O. Keep a bowl and a syringe filled with saline in case of tube fires.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient presents with Ca larynx involving the left false cord, left arytenoid and left aryepiglottic fold with bilateral mobile true cords. The treatment of choice in this patient is which of the following?", "options": [{"label": "A", "text": "Vertical Hemilaryngectomy", "correct": false}, {"label": "B", "text": "Horizontal Partial Hemilaryngectomy", "correct": true}, {"label": "C", "text": "Total Laryngectomy", "correct": false}, {"label": "D", "text": "Radiotherapy followed by chemotherapy", "correct": false}], "correct_answer": "B. Horizontal Partial Hemilaryngectomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Horizontal Partial Hemilaryngectomy Excision of the supraglottis, i.e. epiglottis, aryepiglottic folds, false cords and ventricle—a sort of transverse section of the larynx above the vocal cords (partial horizontal laryngectomy)</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The most common cause of laryngeal stridor in a 60-year-old male is:", "options": [{"label": "A", "text": "Nasopharyngeal Carcinoma", "correct": false}, {"label": "B", "text": "Thyroid Carcinoma", "correct": false}, {"label": "C", "text": "Foreign body aspiration", "correct": false}, {"label": "D", "text": "Carcinoma Larynx", "correct": true}], "correct_answer": "D. Carcinoma Larynx", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Carcinoma Larynx The most common cause for stridor in 60-year-old males will be carcinoma larynx, as carcinoma larynx occurs in males (predominantly) at 40–70 years old. The most common and earliest symptom of subglottic cancer is a stridor.</p>\n<p><strong>Highyeild:</strong></p><p>Classification Of Sites And Various Subsites Under Each Site In Larynx (Ajcc Classification, 2002) Site Subsite Supraglottis ● Suprahyoid epiglottis (both lingual and laryngeal surfaces) ● Infrahyoid epiglottis ● Aryepiglottic folds (laryngeal aspect only) ● Arytenoids ● Ventricular bands (or false cords) Glottis True vocal cords including anterior and posterior commissure Subglottis Subglottis up to lower border of cricoid cartilage Tnm Classification Of Cancer Larynx (American Joint Committee On Cancer, 2002) Supraglottis T 1 Tumour limited to one subsite of supraglottis with normal vocal cord mobility. T 2 Tumour invades mucosa of more than one adjacent subsites of supraglottis or glottis or region outside the supraglottis (e.g., mucosa of base of tongue, vallecula, medial wall of pyriform sinus) without fixation of the larynx. T 3 Tumour limited to larynx with vocal cord fixation and/or invades any of the following: postcricoid area, pre-epiglottic tissues, paraglottic space and/or minor thyroid cartilage invasion. Т 4 а Tumour invades through the thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscle of tongue, strap muscles, thyroid or oesophagus). Т 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures. Glottis T 1 Tumour limited to vocal cord(s) (may involve anterior or posterior commissures) with normal mobility T 1 a Tumour limited to one vocal cord. T 1 b Tumour involves both vocal cords. T 2 Tumour extends to supraglottis and/or subglottis, and/or with impaired vocal cord mobility. T 3 Tumour limited to the larynx with vocal cord fixation and/or invades paraglottic space and/or minor thyroid cartilage erosion. T 4 a Tumour invades through thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscles of the tongue, strap muscles, thyroid, or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures Subglottis T₁ Tumour limited to the subglottis. Τ 2 Tumour extends to vocal cord(s) with normal or impaired mobility T 3 Tumour limited to larynx with vocal cord fixation. T 4 a Tumour invades cricoid or thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscle of tongue, strap muscles, thyroid or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures Regional lymph nodes (N) Nx Regional lymph nodes cannot be assessed. N 0 No regional lymph node metastasis. N 1 Metastasis in a single ipsilateral lymph node, 3 cm or less in greatest dimension. N 2 Metastasis in a single ipsilateral lymph node, more than 3 cm but not more than 6 cm in greatest dimension, or multiple ipsilateral lymph nodes, none more than 6 cm in greatest dimension, or bilateral or contralateral lymph N 2 a Metastasis in a single ipsilateral lymph node more than 3 cm but not more than 6 cm in greatest dimension. N 2 b Metastasis in multiple ipsilateral lymph nodes, none more than 6 cm in greatest dimension. N 2 c Metastasis in bilateral or contralateral lymph nodes, none more than 6 cm in greatest dimension N 3 Metastasis in a lymph node more than 6 cm in greatest dimension. Distant metastasis (M) M x Distant metastasis cannot be assessed M 0 No distant metastasis. M 1 Distant metastasis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Nasopharyngeal cancer does not lead to stridor Option: B. Thyroid cancer causes stridor rarely Option: C. Foreign body aspiration is a common cause of stridor in children, not adults.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A second-year junior resident doctor posted in the department of ENT. A 45-year-old male patient has been posted for a surgical procedure to excise cancerous lesions from the vocal cords. Which laser is most commonly used?", "options": [{"label": "A", "text": "ND Yag", "correct": false}, {"label": "B", "text": "Diode lasers", "correct": false}, {"label": "C", "text": "Co2", "correct": true}, {"label": "D", "text": "Argon laser", "correct": false}], "correct_answer": "C. Co2", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Co2 Carcinoma of the mobile membranous vocal cords can be precisely excised with the CO2 laser under the microscope. It gives the same result as being traditionally treated with radiotherapy. Laser excision has the advantage of lower cost, lower duration of treatment, and morbidity. Endoscopic resection with CO2 laser is most commonly used in laryngeal surgeries.</p>\n<p><strong>Highyeild:</strong></p><p>Lasers In ENT Zones of tissue destruction caused by lasers Various Types Of Lasers And Their Wavelength Type of laser Wavelength ● Argon 488-514 nm ● KTP (Potassium titanyl phosphate) 532 nm ● Nd:YAG (Neodymium: yttrium aluminium garnet) 1060 nm ● CO₂ (Carbon dioxide) 10,600 ● Ho: YAG (Holmium: YAG) 2100 nm ● Er:YAG (Erbium: YAG) 2960 nm ● Diode laser 600-1000 nm ● Tunable dye lasers 577 nm</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Nd Yag laser has been used to debulk tracheobronchial and oesophageal lesions for palliation, hereditary hemorrhagic telangiectasia and turbinectomy. Option: B. Diode lasers have been used in turbinate reduction, laser-assisted stapedectomy and mucosa-intact tonsillar ablation. Option: D. Argon laser is absorbed by haemoglobin and pigmented tissues, and thus it is used to treat port–wine strain, haemangioma and telangiectasias. When focused on a small point, it can vaporise the target tissue.</p>\n<p><strong>Extraedge:</strong></p><p>Precautions In The Use Of Lasers Display a sign outside OT “Lasers In Use.” Close the OT door. No entry or exit of staff permitted. Protective glasses, specific for the wavelength of the laser, Wet saline pads are placed on the eyes. Use a wavelength-specific endotracheal tube or wrap the tube with aluminium foil. Use noninflammable gases such as enflurane. Oxygen concentration in inhaled gases should not exceed 40%. Do not use N2O. Keep a bowl and a syringe filled with saline in case of tube fires.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are a junior resident in the ENT department. A 22-year-old man came to hospital and was diagnosed with laryngeal carcinoma. Which of the following is the false statement regarding carcinoma larynx?", "options": [{"label": "A", "text": "Squamous cell is the most common", "correct": false}, {"label": "B", "text": "Glottis is the most common site", "correct": false}, {"label": "C", "text": "More common in females", "correct": true}, {"label": "D", "text": "Tobacco is an important causative factor", "correct": false}], "correct_answer": "C. More common in females", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>More common in females Laryngeal carcinoma is more common in males, not in females.</p>\n<p><strong>Highyeild:</strong></p><p>TNM staging of cancer larynx Supraglottis T 1 Tumour limited to one subsite of supraglottis with normal vocal cord mobility. T 2 Tumour invades mucosa of more than one adjacent subsites of supraglottis or glottis or region outside the supraglottis (e.g., mucosa of base of tongue, vallecula, medial wall of pyriform sinus) without fixation of the larynx. T 3 Tumour limited to larynx with vocal cord fixation and/or invades any of the following: postcricoid area, pre-epiglottic tissues, paraglottic space and/or minor thyroid cartilage invasion. Т 4 а Tumour invades through the thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscle of tongue, strap muscles, thyroid or oesophagus). Т 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures. Glottis T 1 Tumour limited to vocal cord(s) (may involve anterior or posterior commissures) with normal mobility T 1 a Tumour limited to one vocal cord. T 1 b Tumour involves both vocal cords. T 2 Tumour extends to supraglottis and/or subglottis, and/or with impaired vocal cord mobility. T 3 Tumour limited to the larynx with vocal cord fixation and/or invades paraglottic space and/or minor thyroid cartilage erosion. T 4 a Tumour invades through thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscles of the tongue, strap muscles, thyroid, or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures Subglottis T₁ Tumour limited to the subglottis. Τ 2 Tumour extends to vocal cord(s) with normal or impaired mobility T 3 Tumour limited to larynx with vocal cord fixation. T 4 a Tumour invades cricoid or thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscle of tongue, strap muscles, thyroid or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures Regional lymph nodes (N) Nx Regional lymph nodes cannot be assessed. N 0 No regional lymph node metastasis. N 1 Metastasis in a single ipsilateral lymph node, 3 cm or less in greatest dimension. N 2 Metastasis in a single ipsilateral lymph node, more than 3 cm but not more than 6 cm in greatest dimension, or multiple ipsilateral lymph nodes, none more than 6 cm in greatest dimension, or bilateral or contralateral lymph N 2 a Metastasis in a single ipsilateral lymph node more than 3 cm but not more than 6 cm in greatest dimension. N 2 b Metastasis in multiple ipsilateral lymph nodes, none more than 6 cm in greatest dimension. N 2 c Metastasis in bilateral or contralateral lymph nodes, none more than 6 cm in greatest dimension N 3 Metastasis in a lymph node more than 6 cm in greatest dimension. Distant metastasis (M) M x Distant metastasis cannot be assessed M 0 No distant metastasis. M 1 Distant metastasis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. 90-95% are squamous cell type. Option: B. The most common site is Glottis. Option: D. Risk factors are smoking tobacco and alcohol.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 4-year-old boy was bought by his parents and presented in an emergency with mild respiratory distress. On a laryngoscopy, she was diagnosed with multiple juvenile papillomatoses of the larynx. The next management line is:", "options": [{"label": "A", "text": "Tracheostomy", "correct": false}, {"label": "B", "text": "Microlaryngoscopic Excision", "correct": true}, {"label": "C", "text": "Steroid", "correct": false}, {"label": "D", "text": "Antibiotics", "correct": false}], "correct_answer": "B. Microlaryngoscopic Excision", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Microlaryngoscopic Excision Microlaryngoscopic excision is the next line of management for this patient.</p>\n<p><strong>Highyeild:</strong></p><p>Juvenile Papillomatosis Juvenile papillomatosis is the most common benign neoplasm of the larynx in children. It is viral in origin and is caused by human papilloma DNA virus types 6 and 11. It is presumed that affected children got the disease at birth from their mothers with vaginal human papillomavirus disease. Papillomas mainly affect supraglottic and glottic regions of the larynx but can also involve subglottis, trachea and bronchi. Papillomas are known fothis patient'ss patient'ss patient's pe. Treatment consists of micro-laryngoscopy and CO2 laser excision avoiding injury to the vocal ligament. Supraglottic papillomatosis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Tracheostomy should be avoided as it is associated with the spread of the papillomas to the distal airways. Option: C. Steroids have no role in management. Option: D. Antibiotics also have no role in management.</p>\n<p><strong>Extraedge:</strong></p><p>Besides surgery, various medical therapies are used as adjuvants. Interferon alpha-2a has shown promising results but has several side effects, including fever, chills, myalgia, arthralgia, headache, weight loss, and suppression of bone marrow. Similarly, 13-cis-retinoic acid has been used. This, too, has several side effects.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are a junior resident in the ENT department. A 44-year-old man came to hospital and was diagnosed with carcinoma limited to the glottis of the larynx. Which of the following lymph nodes would be initially involved?", "options": [{"label": "A", "text": "Pretracheal", "correct": false}, {"label": "B", "text": "Upper Jugular", "correct": false}, {"label": "C", "text": "Prelaryngeal", "correct": false}, {"label": "D", "text": "None of the above", "correct": true}], "correct_answer": "D. None of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>None of the above Tumours limited to the glottis usually do not spread to any lymph node station as there are no lymphatics in the vocal cords.</p>\n<p><strong>Highyeild:</strong></p><p>Glottic Cancer Squamous cell carcinoma is the most common malignant tumour of the larynx. Glottis is the most common site of cancer. There are few lymphatics in vocal cords, and nodal metastases are practically never seen in cordal lesions unless the disease spreads beyond the region of the membranous cord. Hoarseness of voice is an early sign because lesions of the cord affect its vibratory capacity. It is because of this that glottic cancer is detected early. However, if the tumour invades the supra or the infraglottic spaces, lymph node spread can occur. Cancer of the larynx. (A) Supraglottic, (B) glottic and (C) subglottic.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is not the reason behind the development of hoarseness of voice in laryngeal carcinoma?", "options": [{"label": "A", "text": "Phonotrauma", "correct": true}, {"label": "B", "text": "Cricoarytenoid Fixation", "correct": false}, {"label": "C", "text": "Recurrent laryngeal nerve involvement", "correct": false}, {"label": "D", "text": "Infiltration of the thyroarytenoid muscle", "correct": false}], "correct_answer": "A. Phonotrauma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Phonotrauma Phonotrauma is not the reason behind the development of hoarseness of voice in laryngeal carcinoma. It occurs due to overuse of voice and is associated with the development of benign lesions such as vocal nodules and cysts.</p>\n<p><strong>Highyeild:</strong></p><p>Hoarseness in supraglottic cancer and subglottic cancer indicates the spread of disease to : Vocal cords with infiltration of the thyroarytenoid muscle. Involvement of recurrent laryngeal nerve. Cricoarytenoid joint involvement. Hoarseness in glottic carcinoma is early because cord lesions affect its vibratory capacity.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Cricoarytenoid joint involvement cause hoarseness of voice in laryngeal carcinoma. Option: C. Involvement of recurrent laryngeal nerve also causes hoarseness of voice in laryngeal carcinoma. Option: D. Vocal cords with infiltration of the thyroarytenoid muscle also cause hoarseness of voice in laryngeal carcinoma.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are posted in the department of ENT. Your ENT professor advised laryngectomy to a 60 years old patient suffering from laryngeal carcinoma. Which of the following conditions would you not recommend a total laryngectomy as the treatment?", "options": [{"label": "A", "text": "Glottic carcinoma with invasion of the thyroid cartilage", "correct": false}, {"label": "B", "text": "Glottic carcinoma involving the posterior commissure", "correct": false}, {"label": "C", "text": "Transglottic tumours with fixation of the vocal cords", "correct": false}, {"label": "D", "text": "Tumour involving both vocal cords with normal mobility", "correct": true}], "correct_answer": "D. Tumour involving both vocal cords with normal mobility", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tumour involving both vocal cords with normal mobility Tumours limited to both vocal cords with normal mobility can be classified as stage T1b . Treatment will include radiotherapy or transoral laser microsurgery . There is no indication of a total laryngectomy.</p>\n<p><strong>Highyeild:</strong></p><p>Total laryngectomy is indicated in the following: All T4 lesions Invasion of thyroid or cricoid cartilage Bilateral arytenoid cartilage involvement Lesions of the posterior commissure Failure after radiotherapy or conservation surgery Transglottic cancers with cord fixation. It is contraindicated in patients with distant metastasis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Invasion of thyroid or cricoid cartilage indicates total laryngectomy. Option: B. Lesions of the posterior commissure also indicate total laryngectomy. Option: C. Transglottic cancers with cord fixation also indicate total laryngectomy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are a junior resident in the ENT department. An adult man came to hospital, and he was diagnosed to have laryngeal carcinoma. Which of the following statements is false regarding supraglottic carcinoma?", "options": [{"label": "A", "text": "Epiglottis, false vocal cords are most commonly involved", "correct": false}, {"label": "B", "text": "Lymphatic spread to upper and middle jugular lymph nodes", "correct": false}, {"label": "C", "text": "Bilateral nodes seen in epiglottic cancer", "correct": false}, {"label": "D", "text": "Hoarseness of voice is an early symptom", "correct": true}], "correct_answer": "D. Hoarseness of voice is an early symptom", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Hoarseness of voice is an early symptom Supraglottic growths are often silent, as hoarseness is a late symptom . So option D is false.</p>\n<p><strong>Highyeild:</strong></p><p>Supraglottic Carcinoma The most common site of supraglottic carcinoma is the epiglottis, followed by false cords and then aryepiglottic folds. Nodal metastases occur early to the upper and middle jugular lymph nodes. Bilateral metastases can be seen in cases of epiglottic cancer. Hoarseness of voice is a late symptom of supraglottic carcinoma. Supraglottic cancer involving epiglottis and right aryepiglottic fold.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. The most common site of supraglottic carcinoma is the epiglottis, followed by false cords and then aryepiglottic folds. Option: B. Nodal metastases occur early to the upper and middle jugular lymph nodes. Option: C. Bilateral metastases can be seen in cases of epiglottic cancer.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are a resident doctor in the ENT department. A 32-year-old man comes to the hospital and he was diagnosed to have juvenile papillomatosis. True about juvenile laryngeal papillomatosis is :", "options": [{"label": "A", "text": "Caused by Human papilloma virus", "correct": false}, {"label": "B", "text": "Microlaryngoscopic surgery is the treatment of choice", "correct": false}, {"label": "C", "text": "Lower respiratory tract can be involved", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above All the statements described above are true.</p>\n<p><strong>Highyeild:</strong></p><p>Juvenile Papillomatosis Juvenile papillomatosis is the most common benign neoplasm of the larynx in children. It is viral in origin and is caused by human papilloma DNA virus type 6 and 11. It is presumed that affected children got the disease at birth from their mothers who had vaginal human papilloma virus disease. Papillomas mostly affect supraglottic and glottic regions of larynx but can also involve subglottis, trachea and bronchi . Papillomas are known for recurrence but rarely undergo malignant change. Treatment consists of microlaryngoscopy and CO2 laser excision avoiding injury to vocal ligament. Supraglottic papillomatosis</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Juvenile laryngeal papillomatosis is caused by Human papillomavirus is a true statement. Option: B. Microlaryngoscopic surgery is the treatment of choice is a true statement. Option: C. At times, due to the predominant involvement of the lower respiratory tract, laryngeal papillomatosis may mimic asthma in the absence of symptoms of hoarseness and stridor.</p>\n<p><strong>Extraedge:</strong></p><p>Besides surgery, various medical therapies are being used as adjuvants. Interferon alpha-2a has shown promising results but has several side effects, including fever, chills, myalgia, arthralgia, headache, weight loss, and suppression of bone marrow. Similarly, 13-cis-retinoic acid has been used. This, too, has several side effects.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 22 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 2</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Laryngeal Papilloma - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 2</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 2 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Topical treatment for recurrent respiratory papillomatosis includes:", "options": [{"label": "A", "text": "Acyclovir", "correct": false}, {"label": "B", "text": "Cidofovir", "correct": true}, {"label": "C", "text": "Ranitidine", "correct": false}, {"label": "D", "text": "Zinc", "correct": false}], "correct_answer": "B. Cidofovir", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cidofovir Cidofovir is very effective in the treatment of Recurrent Respiratory Papillomatous lesions.</p>\n<p><strong>Highyeild:</strong></p><p>Cidofovir It is a nucleoside analogue with broad-spectrum activity against various DNA viruses, interfering with viral DNA synthesis. It can be used systemically, intralesionally and Topically. Adverse effects- Renal toxicity</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A five-year-old male child presented with a three-month history of hoarseness of voice and one-week history of stridor; the endoscopic Image is shown below. The most likely diagnosis is:", "options": [{"label": "A", "text": "Carcinoma Larynx", "correct": false}, {"label": "B", "text": "Respiratory Papillomatosis", "correct": true}, {"label": "C", "text": "Vocal Nodule", "correct": false}, {"label": "D", "text": "Vocal polyp", "correct": false}], "correct_answer": "B. Respiratory Papillomatosis", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003850641-QTDE049003IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Respiratory Papillomatosis The image shows multiple papillomas all over the larynx. So it is respiratory papillomatosis.</p>\n<p><strong>Highyeild:</strong></p><p>Juvenile Laryngeal Papilloma Juvenile laryngeal papilloma is children's most common benign tumor of the larynx. It is seen at 2 to 5 years of age. It is caused by human papillomavirus subtype 6 and 11, and 11 is more aggressive.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option A. Carcinoma larynx usually presents in males between 50 to 70 years of age—risk factors include smoking, alcohol infection with HPV, and irradiation to the neck. Option C. Vocal nodules appear symmetrically on the free edge of the vocal cord, at the junction of the anterior one-third, with the posterior two-thirds, as this is the area of maximum cord vibration and is thus subject to maximum trauma. Option D. Vocal polyp is unilateral, arising from the same position as a vocal nodule. It is soft, smooth and often pedunculated. It may flop up and down the glottis during respiration or phonation. Hoarseness is a common symptom.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 12 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 7</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Laryngectomy - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 7</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 7 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "You are posted in the department of ENT. Your ENT professor advised laryngectomy to a 60 years old patient suffering from laryngeal carcinoma. All of the following can occur as a complication after laryngectomy except:", "options": [{"label": "A", "text": "Loss of voice", "correct": false}, {"label": "B", "text": "Hyposmia", "correct": false}, {"label": "C", "text": "Fistula", "correct": false}, {"label": "D", "text": "Oesophageal stenosis", "correct": true}], "correct_answer": "D. Oesophageal stenosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Oesophageal stenosis Oesophageal stenosis is not a complication after laryngectomy.</p>\n<p><strong>Highyeild:</strong></p><p>COMPLICATIONS AFTER LARYNGECTOMY Loss of voice Aspiration → pneumonia Restricted neck movement Carotid blow out Hyposmia Fistula formation Permanent tracheostomy Psychological trauma</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Loss of voice occurs as a complication after laryngectomy. Option: B. Hyposmia also occurs as a complication after laryngectomy. Option: C- Fistula formation also may occur as a complication after laryngectomy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An ENT surgeon worries about the spread of laryngeal carcinoma to the pre-epiglottic space. Which of the following structures do not border this space?", "options": [{"label": "A", "text": "Hyoepiglottic Ligament", "correct": false}, {"label": "B", "text": "Suprahyoid Epiglottis", "correct": true}, {"label": "C", "text": "Thyroid", "correct": false}, {"label": "D", "text": "Thyrohyoid Membrane", "correct": false}], "correct_answer": "B. Suprahyoid Epiglottis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Suprahyoid Epiglottis Suprahyoid epiglottis does not form a boundary of the pre-epiglottic space .</p>\n<p><strong>Highyeild:</strong></p><p>The pre-epiglottic space is bounded: Superiorly by the hyoepiglottic ligament Anteriorly by the thyroid and thyrohyoid membrane Posteromedially by the infrahyoid epiglottis and quadrangular membrane The paraglottic space is bounded: Laterally by thyroid Medially by ventricle and quadrangular membrane Inferomedially by conus elastics Posteriorly by pyriform sinus.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Pre-epiglottic space is bounded superiorly by the hyoepiglottic ligament. Option: C. Pre-epiglottic space is bounded anteriorly by the thyroid. Option: D. Pre-epiglottic space is bounded anteriorly by the thyrohyoid membrane.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 74-year-old male patient with hoarseness of voice for four weeks is diagnosed with carcinoma larynx. After a total laryngectomy, he loses his speech completely. He fails to learn the esophageal speech for rehabilitation. Which device is shown in the image recommended by the doctor for vocal rehabilitation?", "options": [{"label": "A", "text": "Transoral Pneumatic Device", "correct": false}, {"label": "B", "text": "Electrolarynx", "correct": true}, {"label": "C", "text": "Blom Singer Prosthesis", "correct": false}, {"label": "D", "text": "Panje Prosthesis", "correct": false}], "correct_answer": "B. Electrolarynx", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004330362-QTDE069005IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Electrolarynx The image shown above is of the electrolarynx.</p>\n<p><strong>Highyeild:</strong></p><p>ELECTROLARYNX Electrolarynx is a transistorized, battery-operated portable device. Its vibrating disc is held against the neck's soft tissues, and a low-pitched sound is produced in the hypopharynx, which is further modulated into speech by the tongue, lips, teeth, and palate. (A) An electrolarynx. (B) A laryngectomised patient using the electrolarynx to produce sound. .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Transoral pneumatic device: Here, vibrations produced in a rubber diaphragm are carried by a plastic tube into the back of the oral cavity, where modulators convert sound into speech. This is a pneumatic type of device and uses expired air from the tracheostome to vibrate the diaphragm. Option: C. In Blom singer prosthesis, Voice is produced by temporarily blocking the stoma so that exhaled air from the lungs can be directed from the trachea through the prosthesis into the esophagus and then out through the mouth. Option: D. Panje prosthesis has a flanged tube with a one-way valve that can be inserted through the fistula. It is supplied with an introducer which makes insertion simple. It can be removed, cleaned, and re-inserted by the patient.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 32-year-old female who had done a laryngectomy came to OPD complaining of loss of smell and taste. What would you advise her?", "options": [{"label": "A", "text": "Nothing can be done", "correct": false}, {"label": "B", "text": "Polite Yawning", "correct": false}, {"label": "C", "text": "NAIM", "correct": false}, {"label": "D", "text": "Both BC", "correct": true}], "correct_answer": "D. Both BC", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Both B&C The smell is lost after laryngectomy as air does not pass through the nose. The patient loses the taste and flavors of the food with a loss of appetite. He is also disadvantaged, unaware of environmental dangers, such as smoke, toxic gases, etc. In such cases, a simple maneuver called polite yawning can be taught, whereby some air passes through the nose . This Manoeuvre consists of lowering the jaw along with its attached muscles of the floor of the mouth and tongue with closed lips. This creates negative pressure in the oral cavity and oropharynx. The patient has to perform the technique several times to induce an inflow of air through the nose. A nasal airflow-inducing maneuver (NAIM) rapidly increases oral cavity volume when the lips are closed airtight. The potential vacuum, prompted by the expansion of the oral cavity, must be filled, resulting in airflow through the nasal cavity. Repeating this maneuver rapidly creates a pumping effect, and sufficient airflow through the nose is established to enable smell.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45-year-old male who is a chronic smoker presented with complaints of throat pain, difficulty swallowing, and hoarseness. On examination, there was a lesion on the vocal cords and glottis. It was diagnosed as carcinoma larynx based on biopsy reports. He had a total laryngectomy, and now he came with a loss of smell. What might be the reason for the loss of smell?", "options": [{"label": "A", "text": "Damage to the olfactory nerve", "correct": false}, {"label": "B", "text": "Air bypassing the nose", "correct": true}, {"label": "C", "text": "Decreased Olfactory Receptors", "correct": false}, {"label": "D", "text": "Psychological", "correct": false}], "correct_answer": "B. Air bypassing the nose", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Air bypassing the nose The smell is lost after laryngectomy as air does not pass through the nose . The patient loses the taste and flavors of the food with a loss of appetite . He is also disadvantaged, unaware of environmental dangers, such as smoke and toxic gases.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Olfactory nerves originate in the receptors of the olfactory epithelium and pass through the olfactory foramina in the cribriform plate of the ethmoid bone, ending at the olfactory bulbs. They are not damaged in laryngectomy. Option: C. Olfactory receptors—small, slender nerve cells embedded in large numbers (about 100 million in the rabbit) in the mucous membrane's epithelium lining the nasal cavity's upper part. Each olfactory receptor cell emits two processes (projections). It is not affected in laryngectomy. Option: D. As there is a possibility of anosmia after laryngectomy, we have to advise first for the polite yawning method rather than thinking of this as psychological.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45-year-old male came to OPD with hoarseness for more than two weeks and dysphagia. On examination, there is impaired cord mobility. The biopsy showed a T2 lesion of the glottic region. The doctor planned for a front lateral laryngectomy. What does the impaired mobility of the cord indicate in this patient?", "options": [{"label": "A", "text": "Invasion of laryngeal muscles", "correct": true}, {"label": "B", "text": "Limited Growth", "correct": false}, {"label": "C", "text": "Invasion of thyroid cartilage", "correct": false}, {"label": "D", "text": "Damage to laryngeal nerves", "correct": false}], "correct_answer": "A. Invasion of laryngeal muscles", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Invasion of laryngeal muscles Cord mobility is essential in determining the outcome of T2 lesions . Impaired mobility indicates deeper invasion into intrinsic laryngeal muscles or para glottic space and, thus, poor response to radiation . Partnering para glottic or subglottic space is also associated with an undetected attack of laryngeal cartilages and hence poor survival results . With radiation, the cure rate of T2 lesions with normal cord mobility is 86%, which drops to 63% if cord mobility is impaired.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Normal cord mobility suggests that surface growth is limited. The vocal cord exhibits a reciprocating respiratory motion, abducting in inspiration and returning to the intermediate position in expiration. Option:C. Thyroid cartilage invasion does not affect vocal cord mobility. Option: D, In this case, the nerve damage doesn't cause impaired mobility, but the invasion of the laryngeal muscle affects mobility.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 54-year-old male came with hoarseness, halitosis, and stridor for three weeks. On examination, upper jugular nodes show metastatic deposits. Chest X-ray shows pulmonary metastasis and a total laryngectomy was done. Which might be the communication methods advised for the patient after surgery?", "options": [{"label": "A", "text": "Aphonic Lip Speech", "correct": false}, {"label": "B", "text": "Oesophageal Speech", "correct": false}, {"label": "C", "text": "Electrolarynx", "correct": false}, {"label": "D", "text": "All of These", "correct": true}], "correct_answer": "D. All of These", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of These All the options mentioned above may be used as a communication method after total laryngectomy.</p>\n<p><strong>Highyeild:</strong></p><p>Communication Methods After Total Laryngectomy Aphonic lip speech - trapping air in the buccal cavity and often combined with sign language. Oesophageal speech - the patient is taught to swallow air, hold it in the upper esophagus, and then slowly eject it into the pharynx. The patient can speak 6-10 words before swallowing air. The voice is rough but loud and understandable. Electrolarynx - It is a transistorized, battery-operated portable device. Its vibrating disc is held against the neck's soft tissues, and a low-pitched sound is produced in the hypopharynx, which is further modulated into speech by the tongue, lips, teeth, and palate. Other methods are Written language (pen and paper) Transoral pneumatic device : Here, vibrations produced in a rubber diaphragm are carried by a plastic tube into the back of the oral cavity, where modulators convert sound into speech. Trachea esophageal speech : Here, an attempt is made to carry air from the trachea to the esophagus or hypopharynx by creating a skin-lined fistula or prosthesis. Blom singer, Panje prosthesis : to shunt air from the trachea to the esophagus. They have inbuilt valves which work only in one direction, thus preventing aspiration problems.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Aphonic lip speech is by trapping air in the buccal cavity and is often combined with sign language. Option: B. In Oesophageal speech, the patient is taught to swallow air, hold it in the upper esophagus, and slowly eject it into the pharynx. The patient can speak 6-10 words before re-swallowing air. The voice is rough but loud and understandable. Option: C. Electrolarynx is a transistorized, battery-operated portable device. Its vibrating disc is held against the neck's soft tissues, and a low-pitched sound is produced in the hypopharynx, which is further modulated into speech by the tongue, lips, teeth, and palate.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 17 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 5</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Laryngoscopy - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 5</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 5 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 53 year old male came to OPD with a harsh-sounding voice, shooting pain from ear to ear, and neck pain. It was diagnosed as a vocal polyp. Laryngoscopy was done to remove the lesion. What might be the reason for keeping the patient in a coma position postoperatively?", "options": [{"label": "A", "text": "Rest for the patient", "correct": false}, {"label": "B", "text": "Prevent movements of the spine", "correct": false}, {"label": "C", "text": "Prevent Bleeding", "correct": false}, {"label": "D", "text": "Prevent Aspiration", "correct": true}], "correct_answer": "D. Prevent Aspiration", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Prevent Aspiration After doing a laryngoscopy Patient is kept in a coma position to prevent aspiration of blood or secretions.</p>\n<p><strong>Highyeild:</strong></p><p>POSTOPERATIVE CARE The patient is kept in a coma position to prevent aspiration of blood or secretions. The patient’s respiration should be watched for any laryngeal spasms and cyanosis. Trauma to the larynx, especially if repeated attempts at laryngoscopy have been made. It may lead to laryngeal edema and respiratory distress. Bleeding may occur from the operative site. The patient may spit blood. Care should be taken to prevent aspiration.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A .Rest is usually given after the postoperative care Option: B .Spine movements are not affected here. Option: C. Bleeding can occur from the operative site. But there is no benefit in bleeding if the patient is put in a coma.</p>\n<p><strong>Extraedge:</strong></p><p>COMPLICATIONS OF LARYNGOSCOPY Injury to lips and tongue if they are nipped between the teeth and the laryngoscope. Injury to teeth. They may get dislodged and fall into the pharynx. Bleeding. Laryngeal edema.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 22 year old female presented with complaints of hoarseness, shortness of breath, and stridor. The clinician diagnosed it as laryngeal stricture and planned direct laryngoscopy to dilate the strictures. While performing this procedure, by how much height the patient's head should be elevated?", "options": [{"label": "A", "text": "8-10 cm", "correct": false}, {"label": "B", "text": "10-15 cm", "correct": true}, {"label": "C", "text": "15-18cm", "correct": false}, {"label": "D", "text": "6-8 cm", "correct": false}], "correct_answer": "B. 10-15 cm", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>10-15 cm The patient lies supine . The head is elevated by 10-15 cm by placing a pillow under the occiput or by raising the head flap of the operation table. The neck is flexed on the thorax and the head is extended on the atlantooccipital joint (barking-dog position). Direct laryngoscopy</p>\n<p><strong>Highyeild:</strong></p><p>INDICATIONS OF DIRECT LARYNGOSCOPY DIAGNOSTIC When indirect laryngoscopy is not possible as in infants and young children, and the symptomatology points to the larynx and/or hypopharynx, e.g. hoarseness, dyspnoea, stridor, and dysphagia. When indirect laryngoscopy has not been successful, e.g. due to excessive gag reflex or overhanging epiglottis obscuring a part or complete view of the larynx. To examine hidden areas of: (a) Hypopharynx . The base of the tongue, valleculae, and lower part of pyriform fossa. (b) Larynx . Infrahyoid epiglottis, anterior commissure, ventricles and subglottic region. To find the extent of growth and take a biopsy. THERAPEUTIC Removal of benign lesions of the larynx, e.g. papilloma, fibroma, vocal nodule, polyp or cyst. Removal offoreign bodies from larynx and hypopharynx. Dilatation of laryngeal strictures.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following statements is true regarding the procedure shown in the image given below?", "options": [{"label": "A", "text": "The head is flexed at the atlantooccipital joint", "correct": false}, {"label": "B", "text": "The instrument shown is held in the right hand", "correct": false}, {"label": "C", "text": "The head is extended at the atlantooccipital joint", "correct": true}, {"label": "D", "text": "The instrument is introduced through the middle of the mouth", "correct": false}], "correct_answer": "C. The head is extended at the atlantooccipital joint", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994005045-QTDE004003IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>The head is extended at the atlantooccipital joint. The image shown above is showing a direct laryngoscopy For doing direct laryngoscopy procedure Neck is flexed on the thorax and the head extended on the atlanto-occipital joint (barking-dog position).</p>\n<p><strong>Highyeild:</strong></p><p>INDICATIONS OF DIRECT LARYNGOSCOPY DIAGNOSTIC When indirect laryngoscopy is not possible as in infants and young children, and the symptomatology points to the larynx and/or hypopharynx, e.g. hoarseness, dyspnoea, stridor, and dysphagia. When indirect laryngoscopy has not been successful, e.g. due to excessive gag reflex or overhanging epiglottis obscuring a part or complete view of the larynx. To examine hidden areas of: (a) Hypopharynx . The base of the tongue, valleculae, and lower part of pyriform fossa. (b) Larynx . Infrahyoid epiglottis, anterior commissure, ventricles and subglottic region. To find the extent of growth and take a biopsy. THERAPEUTIC Removal of benign lesions of the larynx, e.g. papilloma, fibroma, vocal nodule, polyp or cyst. Removal offoreign bodies from larynx and hypopharynx. Dilatation of laryngeal strictures.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Head is extended on the atlantooccipital joint; not flexed. Option: B. Laryngoscope is held in the left hand. Option: D. laryngoscope must be introduced from the right angle of the mouth; not through the middle of the mouth.</p>\n<p><strong>Extraedge:</strong></p><p>COMPLICATIONS OF LARYNGOSCOPY Injury to lips and tongue if they are nipped between the teeth and the laryngoscope. Injury to teeth. They may get dislodged and fall into the pharynx. Bleeding. Laryngeal edema.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is difficult to visualize or examine on indirect laryngoscopy?", "options": [{"label": "A", "text": "True vocal cord", "correct": false}, {"label": "B", "text": "Anterior Commissure", "correct": true}, {"label": "C", "text": "Epiglottis", "correct": false}, {"label": "D", "text": "False vocal cord", "correct": false}], "correct_answer": "B. Anterior Commissure", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Anterior Commissure Hidden areas of the larynx viz. infrahyoid epiglottis, anterior commissure, ventricles and subglottic region, and apex of the pyriform fossa are difficult to visualize by indirect laryngoscopy.</p>\n<p><strong>Highyeild:</strong></p><p>INDIRECT LARYNGOSCOPIC MIRROR</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Identify The Structures Visualized With The Instrument Shown? a. Epiglottis b. Corniculate and Cuneiform cartilages c. Base of tongue d. Posterior end of nasal septum e. Medial and lateral glossoepiglottic folds Select the correct answer from the given below code:", "options": [{"label": "A", "text": "A, B, And D", "correct": false}, {"label": "B", "text": "A, B, C, and D", "correct": false}, {"label": "C", "text": "A, B, C, and E", "correct": true}, {"label": "D", "text": "A, B, C, D, E", "correct": false}], "correct_answer": "C. A, B, C, and E", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1684994005084-QTDE004005IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>A, B, C, and E The image shown above is of the indirect laryngoscopic mirror . Epiglottis, corniculate cartilage, cuneiform cartilage, the base of the tongue, and median and lateral glossoepiglottic folds can be seen on indirect laryngoscopy. The posterior end of the nasal septum cannot be seen on indirect laryngoscopy.</p>\n<p><strong>Highyeild:</strong></p><p>STRUCTURES SEEN IN INDIRECT LARYNGOSCOPY</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 15 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 4</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Laser in Ent & Photodynamic Therapy - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 4</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 4 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Photodynamic therapy is an upcoming newer modality for treating cancer of the skin, larynx, nasopharynx, aerodigestive tract and endobronchial tumours. A cancer patient in the ENT ward wants to understand more about this. All the following statements with respect to the treatment modality are true, except: It is based on injecting a photosensitising agent, which is taken up preferentially by the tumour cells and then exposing the site to a specific wavelength of the laser. Laser activates the photosensitising agent, which destroys cancer cells but spares the normal tissues. Photodynamic therapy has also been used for recurrences after surgery, radiation or chemotherapy. Laser often used in photodynamic therapy is an argon-tunable dye laser with a wavelength of 630 nm. Photosensitizing agents used intravenously include haematoporphyrin derivative (for head and neck cancers) and photosan-3 (for endobronchial tumours).", "options": [{"label": "A", "text": "Statement III", "correct": false}, {"label": "B", "text": "Statement IV", "correct": false}, {"label": "C", "text": "Statement V", "correct": false}, {"label": "D", "text": "None of the above", "correct": true}], "correct_answer": "D. None of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>None of the above All the five options above are true . Therefore D (none of the above) is the answer.</p>\n<p><strong>Highyeild:</strong></p><p>Photodynamic Therapy: - It is an upcoming newer modality of treating skin cancer, larynx, nasopharynx, aerodigestive tract and endobronchial tumours. It is based on the principle of injecting a photosensitising agent, which is taken up preferentially by the tumour cells and then exposing the site to a specific wavelength of the laser. The laser activates the photosensitising agent, which destroys cancer cells but spares the normal tissues. Photodynamic therapy has also been used for recurrences after surgery, radiation or chemotherapy. Photosensitising agents used intravenously include haematoporphyrin derivative (for head and neck cancers) and photosan-3 (for endobronchial tumours). A topical sensitiser, delta-aminolevulinic acid, has been used for skin cancers (basal cell carcinoma and Bowen disease). An argon-tunable dye laser with a wavelength of 630 nm is often used in photodynamic therapy. It also has the advantage of delivery through flexible fibres. Also, lasers with different wavelengths can be produced by changing the dye. Patients receiving photodynamic therapy should avoid exposure to sunlight and use sun-protective clothing to avoid photosensitive skin reactions, which may continue for several weeks.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Use of lasers in surgery has revolutionised medicine. But a lot of precautions need to be maintained in the OT to prevent any accidents from happening. All of the following are true regarding precautions in the use of lasers, except:", "options": [{"label": "A", "text": "Use nitrous oxide as an anaesthetic agent.", "correct": true}, {"label": "B", "text": "Use a wavelength-specific endotracheal tube or wrap the tube with aluminium foil.", "correct": false}, {"label": "C", "text": "Wet, saline-soaked towels for the face or exposed parts of the skin.", "correct": false}, {"label": "D", "text": "Use methylene blue-coloured saline for the cuff of the endotracheal tube.", "correct": false}], "correct_answer": "A. Use nitrous oxide as an anaesthetic agent.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Use nitrous oxide as an anaesthetic agent. Nitrous oxide is contraindicated as an anaesthetic agent in cases where the laser is used as nitrous oxide is an oxidising gas and can be inflammable.</p>\n<p><strong>Highyeild:</strong></p><p>Precautions In The Use Of Lasers Display a sign outside OT “Lasers In Use.” Close the OT door. No entry or exit of staff permitted. Protective glasses, specific for the laser's wavelength, should be worn by the surgeon, nurses and other OT personnel. Glasses should have side protectors. Wet saline pads are placed on the eyes. Use noninflammable gases such as enflurane. Oxygen concentration in inhaled gases should not exceed 40%. Halothane or enflurane are used as they are noninflammable. Nitrous oxide gas, being oxidising, is not used. Do not use N2O. Use a wavelength-specific endotracheal tube or wrap the tube with aluminium foil. Wet saline-soaked towels for the face or exposed parts of the skin. Use methylene blue-coloured saline for the cuff of the endotracheal tube. Keep a bowl and a syringe filled with saline in case of tube fires.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. Using a wavelength-specific endotracheal tube or wrapping the tube with aluminium foil is a true statement. Option C. Wet saline-soaked towels for the face or exposed parts of the skin is also a true statement. Option D. Using methylene blue-coloured saline for the endotracheal tube cuff is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Laser is a cutting-edge technology being used in OT currently. The ENT department has set up special training workshops for doctors to learn about its operation. Enumerated below are the advantages of lasers, except:", "options": [{"label": "A", "text": "Rapid ablation of tissues", "correct": false}, {"label": "B", "text": "Special training in operating with lasers", "correct": true}, {"label": "C", "text": "Some lasers can be passed through optical fibres", "correct": false}, {"label": "D", "text": "Minimal oedema of tissues", "correct": false}], "correct_answer": "B. Special training in operating with lasers", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Special training in operating with lasers The requirement of special training is a disadvantage , not an advantage.</p>\n<p><strong>Highyeild:</strong></p><p>Advantages And Disadvantages Of Lasers Advantages include Precise incision, easy and rapid ablation of tissues, excellent haemostasis, and minimal postoperative pain and oedema of tissues. Some lasers can be passed through optical fibres and can thus be used through flexible endoscopes and straight or curved tubes to ablate tumours situated under challenging locations in the tracheobronchial tube or nasal crevices or clefts. Disadvantages include High cost in the purchase of equipment and its maintenance Special training in operating lasers Hazards in the use of laser require special precautions Safety measures and special anaesthesia requirements to avoid fires</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Easy and rapid ablation of tissues is an advantage. Option C. Some lasers can be passed through optical fibres and can thus be used through flexible endoscopes and straight or curved tubes to ablate tumours situated under challenging locations in the tracheobronchial tube or nasal crevices or clefts. So it is an advantage. Option D., Minimal postoperative pain and oedema of tissues also is an advantage.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "As a PG in the ENT department, you are undergoing special training to learn about the usage of lasers in cancer treatment. The lasers enumerated below are all belonging to the invisible spectrum, except:", "options": [{"label": "A", "text": "ND: Yag", "correct": false}, {"label": "B", "text": "HO: Yag", "correct": false}, {"label": "C", "text": "Co2", "correct": false}, {"label": "D", "text": "KTP", "correct": true}], "correct_answer": "D. KTP", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>KTP KTP belongs to the visible spectrum.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. ND: Yag laser belongs to the invisible spectrum. Option B. HO: Yag laser belongs to the invisible spectrum. Option C. Co2 laser belongs to the invisible spectrum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 14 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 5</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Mastoid Anatomy - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 5</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 5 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 40-year-old patient with mastoiditis with pulsatile otorrhoea undergoes mastoidectomy. In this operation, Macewen's triangle is the landmark for", "options": [{"label": "A", "text": "Maxillary Sinus", "correct": false}, {"label": "B", "text": "Mastoid Antrum", "correct": true}, {"label": "C", "text": "Frontal Sinus", "correct": false}, {"label": "D", "text": "Cochlea", "correct": false}], "correct_answer": "B. Mastoid Antrum", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mastoid Antrum MacEwen's (supramental) triangle is an important landmark to locate the mastoid antrum in mastoid surgery. This is also the site of tenderness in acute mastoiditis.</p>\n<p><strong>Highyeild:</strong></p><p>MACEWEN’S/ SUPRAMEATAL TRIANGLE MacEwen's (supramental) triangle is bounded by: Superior: Temporal line Anterior: Posterosuperior segment of the bony external auditory canal. Posterior: The line is drawn as a tangent to the external canal. It is an important landmark to locate the mastoid antrum in mastoid surge It is an important landmark to locate the mastoid antrum in mastoid surgery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Not correctly matched pair is:", "options": [{"label": "A", "text": "Utricle and saccule -Semicircular canal", "correct": false}, {"label": "B", "text": "Oval window -Footplate of stapes", "correct": false}, {"label": "C", "text": "Aditus ad antrum -MacEwen's triangle", "correct": true}, {"label": "D", "text": "Scala vestibule -Reissner's membrane", "correct": false}], "correct_answer": "C. Aditus ad antrum -MacEwen's triangle", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Aditus ad antrum -MacEwen's triangle Aditus ad antrum is an opening through which the attic communicates with the antrum. Mastoid antrum and not the aditus is marked externally on by MacEwen's triangle</p>\n<p><strong>Highyeild:</strong></p><p>MACEWEN’S/ SUPRAMEATAL TRIANGLE MacEwen's (supramental) triangle is bounded by: Superior: Temporal line Anterior: Posterosuperior segment of the bony external auditory canal. Posterior: The line is drawn as a tangent to the external canal. It is an important landmark to locate the mastoid antrum in a mastoid surge</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION A- The utricle lies in the bony vestibule and receives the five openings of the three semicircular ducts/semicircular canals. Saccule also lies in the bony vestibule, anterior to the utricle, and together both of these are called otolith organs. Hence, this pair is correct OPTION B- The oval window is closed by the footplate of the stapes. Hence this pair is also related to each other OPTION D- Reissner's membrane separates the scala vestibule from the scala media. Hence, this pair is also related to each other.</p>\n<p><strong>Extraedge:</strong></p><p>It is an important landmark to locate the mastoid antrum in mastoid surgery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 30-year-old female reported swelling behind her right ear for 6 years. It was painless, gradually and progressively increasing in size, and associated with facial weakness. No history of trauma, headache, hearing impairment, or otorrhea. The tympanic membrane of the right ear was intact. The left ear was normal. The audiometric evaluation showed normal hearing. CT scan revealed that there was bony outgrowth in the mastoid bone and it compressed the facial nerve. Where is the position of the facial nerve in relation to the mastoid cell?", "options": [{"label": "A", "text": "Anterior", "correct": true}, {"label": "B", "text": "Superior", "correct": false}, {"label": "C", "text": "Posterior", "correct": false}, {"label": "D", "text": "Inferior", "correct": false}], "correct_answer": "A. Anterior", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Anterior The horizontal part of the facial nerve runs downward in the medial wall of the The vertical part runs downward behind the tympanum and in front of the mastoid cells and emerges out through the stylomastoid foramen .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION B- Temporal lobe of the brain and meninges are superior OPTION C- Cerebellum is posteromedial to the mastoid air cells. The lateral sinus is posterior to the mastoid cells. OPTION D- Jugular bulb is in close contact with the floor of the tympanum.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 29-year-old female was admitted to the emergency department due to 5 days of right postauricular area swelling. The patient had a previous history of acute otitis media. On examination there was bulging of the tympanic membrane and erythematous change and swelling of the right postauricular area with tenderness were noticed. CT revealed soft tissue density filling the right middle ear and abscess in mastoid air cells located above and below and behind the labyrinth. Name the mastoid air cell in that location.", "options": [{"label": "A", "text": "Perilabyrinthine Cells", "correct": true}, {"label": "B", "text": "Peritubal Cells", "correct": false}, {"label": "C", "text": "Tip Cells", "correct": false}, {"label": "D", "text": "Marginal Cells", "correct": false}], "correct_answer": "A. Perilabyrinthine Cells", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Perilabyrinthine Cells Perilabyrinthine cells are located above, below, and behind the labyrinth , some of them pass through the arch of the superior semicircular canal. These cells may communicate with the petrous apex . If an abscess develops in these mastoid air cells, it can spread and result in a petrous abscess.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- OPTION B- Peritubal cells are around the eustachian tube. Along with hypotympanic cells they also communicate with petrous apex. OPTION C- Tip cells are quite large and lie medial and lateral to the digastric ridge in the tip of the Mastoid. OPTION D- Marginal cells lie behind the sinus plate and may extend into the occipital bone</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 28-year-old female patient presented with complaints of right ear discharge for 8 months which was scanty, purulent, foul-smelling, and associated with intermittent tinnitus. Otomicroscopic examination revealed normal left ear tympanic membrane and perforation in the right. The high-resolution computed tomography showed a thin bridge of bone that divided the petrous and squamous portion of the mastoid air cells at the level of the mastoid antrum. Name the bony plate.", "options": [{"label": "A", "text": "Korner's Septum", "correct": true}, {"label": "B", "text": "Tegmen Tympani", "correct": false}, {"label": "C", "text": "Pyramid", "correct": false}, {"label": "D", "text": "Promontory", "correct": false}], "correct_answer": "A. Korner's Septum", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Korner's Septum Korner septum is the persistence of petrosquamous suture. The petrosquamosal suture may persist as a bony plate called as Korner’s septum . It separates the superficial squamous from deep petrous air cells. Korner’s septum is surgically important as it may cause difficulty in locating the antrum and the deeper cells , and thus may lead to incomplete removal of disease at mastoidectomy Mastoid antrum cannot be reached unless the Korner’s septum has been removed.</p>\n<p><strong>Highyeild:</strong></p><p>KORNER SEPTUM It is the persistent petrosquamous suture. It separates the superficial squamous from deep petrous air cells. It is surgically important as it may cause difficulty in locating the antrum and the deeper cells, and thus lead to incomplete removal of disease at mastoidectomy. Mastoid antrum cannot be reached unless the Korner's septum has been removed.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - OPTION B- The roof of the middle ear is formed by a thin plate of bone called tegmen tympani. It also extends posteriorly to form the roof of the aditus and antrum. It separates the tympanic cavity from the middle cranial fossa. OPTION C- The posterior wall lies close to the mastoid air cells. It presents a bony projection called a pyramid through the summit which appears the tendon of the stapedius muscle to get an attachment to the neck of the stapes. OPTION D- The medial wall is formed by the labyrinth. It presents a bulge called promontory which is due to the basal coil of the cochlea.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 15 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 17</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Ménière’S Disease - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 17</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 17 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "All are true about Meniere’s disease except", "options": [{"label": "A", "text": "Endolymph is produced from stria vascularis", "correct": false}, {"label": "B", "text": "Low frequencies are mostly affected in the early stages", "correct": false}, {"label": "C", "text": "Surgery is the treatment of all active disease", "correct": true}, {"label": "D", "text": "Donaldson line is used for the identification of endolymphatic sac", "correct": false}], "correct_answer": "C. Surgery is the treatment of all active disease", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Surgery is the treatment of all active disease In Meniere's disease surgery is not the treatment for all active diseases. Surgical treatment is only done when medical treatment fails.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Endolymph produced from stria vascularis is a true statement. Option: B. In the early stages, lower frequencies are affected and the curve is of rising type. When higher frequencies are involved curve becomes flat or a falling type in pure tone audiometry. Option: D. Donaldson’s line is the landmark for the endolymphatic sac which lies anterior and inferior to it.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Chemical labyrinthectomy by transtympanic route is done in Meniere’s disease using which drug?", "options": [{"label": "A", "text": "Amikacin", "correct": false}, {"label": "B", "text": "Gentamicin", "correct": true}, {"label": "C", "text": "Amoxycillin", "correct": false}, {"label": "D", "text": "Cyclosporine", "correct": false}], "correct_answer": "B. Gentamicin", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Gentamicin Chemical labyrinthectomy is done by using intratympanic gentamicin therapy.</p>\n<p><strong>Highyeild:</strong></p><p>INTRATYMPANIC GENTAMICIN THERAPY (CHEMICAL LABYRINTHECTOMY) Gentamicin is mainly vestibulotoxic. It has been used in daily or biweekly injections into the middle ear. The drug is absorbed through the round window and causes the destruction of the vestibular labyrinth. Total control of vertigo spells has been reported in 60-80 per cent of patients with some relief from symptoms in others.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The therapy shown is used in the management of which of the following conditions:", "options": [{"label": "A", "text": "Meniere’s Disease", "correct": true}, {"label": "B", "text": "Otosclerosis", "correct": false}, {"label": "C", "text": "Serous Otitis Media", "correct": false}, {"label": "D", "text": "Acute Suppurative Otitis Media", "correct": false}], "correct_answer": "A. Meniere’s Disease", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003276806-QTDE040003IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Meniere’s Disease The given Image shows Meniette device therapy/Intermittent low-pressure pulse therapy which is given for Meniere’s disease .</p>\n<p><strong>Highyeild:</strong></p><p>Meniette device therapy/Intermittent low-pressure pulse therapy Intermittent positive pressure delivered to inner ear fluids brings relief from the symptoms of Ménière’s disease. A prerequisite for such a therapy is to perform a myringotomy and insert a ventilation tube so that the device when coupled to the external ear canal can deliver pressure waves to the round window membrane via the ventilation tube. Pressure waves pass through the perilymph and cause a reduction in endolymph pressure by redistributing it through various communication channels such as the endolymphatic sac or the blood vessels.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Destructive procedures for Meniere's disease are:", "options": [{"label": "A", "text": "Fick's Procedure", "correct": false}, {"label": "B", "text": "Cody Tack Procedure", "correct": false}, {"label": "C", "text": "Vestibular Neuronectomy", "correct": false}, {"label": "D", "text": "Labyrinthectomy", "correct": true}], "correct_answer": "D. Labyrinthectomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Labyrinthectomy Labyrinthectomy e. chemical labyrinthectomy is a destructive procedure for Meniere’s disease .</p>\n<p><strong>Highyeild:</strong></p><p>INTRATYMPANIC GENTAMICIN THERAPY (CHEMICAL LABYRINTHECTOMY) Gentamicin is mainly vestibulotoxic. It has been used in daily or biweekly injections into the middle ear. The drug is absorbed through the round window and causes the destruction of the vestibular labyrinth. Total control of vertigo spells has been reported in 60-80 per cent of patients with some relief from symptoms in others.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Fick’s operation is puncturing the saccule with a needle through the stapes footplate. A distended saccule lies close to the stapes footplate and can be easily penetrated. It is a conservative procedure. Option: B. Cody’s tack procedure consists of placing a stainless steel tack through the stapes footplate. The tack would cause periodic decompression of the saccule when it gets distended. It is also a conservative procedure. Option: C. Vestibular Neuronectomy controls vertigo but preserves hearing. It is also a conservative procedure for Meniere’s disease.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A middle-aged man came to the ENT OPD with complaints of a progressive decrease in hearing over the past two years. He works as a salesman at a store and complains of not being able to hear clearly during conversations with the people around him. On examination with tuning forks, the resident concludes that it is a sensorineural type of hearing loss and on further questioning, the patient also gives a history of imbalance and tinnitus. In this case, we can rule out Meniere's disease and diagnose it as Acoustic Neuroma if-", "options": [{"label": "A", "text": "Stapedial Reflex is absent", "correct": false}, {"label": "B", "text": "Recruitment phenomenon is present", "correct": false}, {"label": "C", "text": "High Sisi Score", "correct": false}, {"label": "D", "text": "Rollover phenomenon is present", "correct": true}], "correct_answer": "D. Rollover phenomenon is present", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rollover phenomenon is present Meniere’s disease is an important differential for Acoustic Neuroma as the presenting symptoms are very similar. Meniere’s is a cochlear pathology whereas Acoustic Neuroma is a retro cochlear pathology. So Roll over phenomenon is seen in Retrocochlear disease i.e. Acoustic Neuroma.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Stapedial Reflex is present in cochlear lesions and absent in Retrocochlear disease. Option: B. The recruitment phenomenon is present in Cochlear pathology. Option: C. SISI (Short increment sensitivity score) is high in cochlear pathologies.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 26 yrs. old female presents with complaints of sudden vertigo with the change in head position which lasts for 2-3 min. What is the management of likely diagnosis-", "options": [{"label": "A", "text": "Dix- Hallpike’S Maneuver", "correct": false}, {"label": "B", "text": "Epley’S Maneuver", "correct": true}, {"label": "C", "text": "Sedatives", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "B. Epley’S Maneuver", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Epley’S Maneuver The probable diagnosis is Benign Paroxysmal Positional Vertigo which is characterized by vertigo with a change in head position. The management is EPLEY’S MANEUVER [ reposition of otoconial debris ].</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Dix- Hallpike’s maneuver is for making the diagnosis of benign paroxysmal positional vertigo. Option: C. Sedatives are not recommended for BPPV.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 32 years male came with complaints of a sudden fall without loss of consciousness, He also has independent complaints of Vertigo which last approx. half an hour and is associated with vomiting. He also listens to clicks multiple times in his right ear. His previous reports show normal otoscopy. On pure tone audiometry, mild sensorineural hearing loss is present. Impedance audiometry and NCCT head are within normal limits. No family history of vertigo is present, No history of any head trauma is present-", "options": [{"label": "A", "text": "Otosclerosis", "correct": false}, {"label": "B", "text": "Meniere’s Disease", "correct": true}, {"label": "C", "text": "BPVV", "correct": false}, {"label": "D", "text": "Acoustic Neuroma", "correct": false}], "correct_answer": "B. Meniere’s Disease", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Meniere’s Disease Ménière’s disease is an idiopathic inner ear disorder characterized by recurrent spontaneous vertigo accompanied by fluctuating or progressive sensorineural hearing loss, tinnitus and aural fullness in the affected ear.</p>\n<p><strong>Highyeild:</strong></p><p>The definitive spell of Meniere’s disease is described as spontaneous rotational vertigo (i.e. sensation of motion when no motion is occurring relative to earth’s gravity) lasting at least 20 minutes. In later stages, drop attacks can develop (Tumarkin crisis). The patient suddenly drops to the ground( without losing consciousness), without associated vertigo. Diagnostic criteria for Ménière's disease Diagnosis Criteria Definite Ménière's disease =Two definitive spontaneous episodes of vertigo lasting 20 minutes to 12 hours + Audiometrically documented low- to medium- frequency sensorineural hearing loss in the affected ear on at least one occasion before, during or after one of the episodes of vertigo + Fluctuating aural symptoms (hearing, tinnitus or fullness) in the affected ear Probable Ménière's disease =Two episodes of vertigo or dizziness, each lasting 20 minutes to 24 hours + Fluctuating aural symptoms (hearing, tinnitus or fullness) in the reported ear</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In otosclerosis, there is conductive hearing loss. Option: C. In BPPV vertigo is usually less than a minute and is usually triggered by head movements such as looking up, or lying down, which is not present in our case. Option: D. NCCT head is normal, so acoustic neuroma is ruled out.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 40-year-old female came to ENT OPD with 6 months of H/O ringing in her ear, a feeling of spinning and hearing loss in the left ear. The spinning sensation is episodic and lasts for 60 minutes. During these episodes, she has nausea and vomiting. She denies fever, chills, cough, and ear or throat pain. She has H/o hypothyroidism and fibromyalgia and is currently on medications like thyroxine and ibuprofen. Her father died of MI at the age of 54. On examination her BP: was 128/70; Pulse was 60/min; respiration was 16/min., and left-sided SN hearing loss. Which of the following is the most likely diagnosis?", "options": [{"label": "A", "text": "Meniere's Disease", "correct": true}, {"label": "B", "text": "Acoustic Neuroma", "correct": false}, {"label": "C", "text": "Migraine", "correct": false}, {"label": "D", "text": "Multiple Sclerosis", "correct": false}], "correct_answer": "A. Meniere's Disease", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Meniere's Disease The typical triad of vertigo, deafness and tinnitus, in this case, is suggestive of Meniere.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: B. Acoustic neuroma does not present with vertigo. Option: C. There is no hearing loss in Migraines. Option: D. Multiple sclerosis will have other intracranial manifestations also.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A middle-aged male patient presented to you with a history of vertigo, tinnitus, and intolerance to loud sounds. He constantly gets vertiginous episodes when exposed to loud sounds or noise. This phenomenon is not seen in which of the following conditions?", "options": [{"label": "A", "text": "Meniere's Disease", "correct": false}, {"label": "B", "text": "Superior semicircular canal dehiscence", "correct": false}, {"label": "C", "text": "Perilymph Fistula", "correct": false}, {"label": "D", "text": "ASOM with a perforated tympanic membrane", "correct": true}], "correct_answer": "D. ASOM with a perforated tympanic membrane", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>ASOM with a perforated tympanic membrane The phenomenon described above is the Tulio phenomenon and is not seen in ASOM with a perforated tympanic membrane.</p>\n<p><strong>Highyeild:</strong></p><p>TULIO PHENOMENA The Tulio phenomenon refers to sound-induced vertigo. People with this condition experience unsteadiness, vertigo, nausea and rapid involuntary eye movements (nystagmus) when exposed to loud sounds or noise. It is due to the distended saccule lying against the stapes footplate. Some cases of Meniere's disease show the Tulio phenomenon. It is also seen whenever there is a third mobile window lesion such as otic syphilis, perilymph fistula and dehiscence of semicircular canals.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Tulio phenomena may be seen in Meniere’s disease. Option: B. Tulio phenomena may also be seen in the dehiscence of semicircular canals. Option: C. Tulio phenomena may also be seen in the perilymph fistula.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 35-year-old woman diagnosed with Meniere’s disease presented with complaints of sudden onset dizziness and sensation of surroundings swirling. Which of the following drugs cannot be used as a vestibular sedative to treat this condition?", "options": [{"label": "A", "text": "Dimenhydrinate", "correct": false}, {"label": "B", "text": "Prochlorperazine", "correct": false}, {"label": "C", "text": "Promethazine", "correct": false}, {"label": "D", "text": "Diphenhydramine", "correct": true}], "correct_answer": "D. Diphenhydramine", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Diphenhydramine Among the given drugs Diphenhydramine is not a vestibular sedative.</p>\n<p><strong>Highyeild:</strong></p><p>Diphenhydramine is an antihistamine used to relieve symptoms of allergy, hay fever, and common cold. It is also used to prevent and treat symptoms of motion sickness. The first line of treatment of an acute episode of Meniere's is symptomatic and hence vestibular sedatives are the mainstay in the treatment. These are Dimenhydrinate Prochlorperazine Promethazine Rarely: Diazepam and atropine</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Dimenhydrinate is a vestibular sedative and hence can be used in Meniere’s disease. Option: B. Prochlorperazine is also a vestibular sedative and hence can be used in Meniere’s disease. Option: C. Promethazine is also a vestibular sedative and hence can be used in Meniere’s disease.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 47-year-old male patient just got diagnosed with early Meniere’s disease. The ENT surgeon ordered a pure tone audiometry test to assess the hearing loss. The most likely finding would be?", "options": [{"label": "A", "text": "Higher Frequencies Affected", "correct": false}, {"label": "B", "text": "Lower frequencies affected", "correct": true}, {"label": "C", "text": "Middle frequencies affected", "correct": false}, {"label": "D", "text": "All Frequencies Affected", "correct": false}], "correct_answer": "B. Lower frequencies affected", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lower frequencies affected Early Ménière's disease affects lower frequencies . The most common audiometric pattern in early Ménière's disease is a low-frequency loss . As the disease progresses, all the frequencies become affected.</p>\n<p><strong>Highyeild:</strong></p><p>PURE TONE AUDIOMETRY IN MENIERE’S DISEASE BOTH AC AND BC ARE BELOW 25 DB. A- B GAP <15 DB. UPWARD SLOPING AUDIOGRAM.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45-year-old man presented with a history of progressive hearing loss over a year, followed by an episode of sudden onset vertigo. He noticed an improvement in hearing after the vertiginous episode. The most likely diagnosis among the following would be?", "options": [{"label": "A", "text": "Lermoyez Syndrome", "correct": true}, {"label": "B", "text": "Lemiere's Syndrome", "correct": false}, {"label": "C", "text": "Lehrich Syndrome", "correct": false}, {"label": "D", "text": "Leigh Syndrome", "correct": false}], "correct_answer": "A. Lermoyez Syndrome", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lermoyez Syndrome The most likely diagnosis would be Lermoyez syndrome. It is a variant of Ménière's wherein the symptoms appear in reverse order . The patient presents with progressive hearing loss followed by an attack of vertigo which causes improvement in hearing.</p>\n<p><strong>Highyeild:</strong></p><p>VARIANTS OF MENIERE’S DISEASE Lermoyez's reverse Meniere syndrome : Deafness—> vertigo —> improvement in hearing. Tumarkin's sudden drop attack : Pt falls without vertigo/loss of consciousness. Meyerhoff's oculo-vestibular response : Vertigo due to optokinetic stimulus. Cochlear hydrops : deafness & tinnitus only.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Lemiere's Syndrome is a condition characterized by infectious thrombophlebitis of the internal jugular vein and bacteremia caused by primarily anaerobic organisms, following a recent oropharyngeal infection. Option: C. Lehrich Syndrome commonly referred to as aortoiliac occlusive disease, is due to thrombotic occlusion of the abdominal aorta just above the site of its bifurcation. Option: D. Leigh Syndrome (subacute necrotizing encephalomyelopathy) is a rare genetic neurometabolic disorder characterized by degeneration of the central nervous system.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 43-year-old patient presented with a history of recurrent episodes of whirling vertigo, which usually are preceded by a variable sense of ear pressure and fullness, decreased hearing, and low-tone roaring tinnitus. A useful test to diagnose this condition would be?", "options": [{"label": "A", "text": "Glycerol Test", "correct": true}, {"label": "B", "text": "Caloric Test", "correct": false}, {"label": "C", "text": "DIX Hallpike Test", "correct": false}, {"label": "D", "text": "All of the above.", "correct": false}], "correct_answer": "A. Glycerol Test", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Glycerol Test The given clinical history points to a diagnosis of Ménière's disease. The glycerol test is useful for the diagnosis of Ménière's disease.</p>\n<p><strong>Highyeild:</strong></p><p>GLYCEROL TEST Glycerol Test : Latient made to drink Glycerol (Osmotic diuretic) ↓ Diuresis after 40 min ↓ Loss of Endolymph ↓ Hydrops shrink back ↓ Symptoms improve Glycerol is an osmotic diuretic which when given orally reduces hydrops and improves symptoms.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B: The caloric test used in Ménière's disease is not diagnostic as it is abnormal in many other conditions like ototoxicity, vestibular schwannoma, vestibular neuritis, etc. Option: C. Dix-Hallpike test is performed to help provide the diagnosis of BPPV. A positive test would render a vertiginous episode and nystagmus observed by the clinician.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In which of the following conditions is endolymphatic sac decompression performed as a conservative procedure?", "options": [{"label": "A", "text": "Meniere's Disease", "correct": true}, {"label": "B", "text": "Perilymph Fistula", "correct": false}, {"label": "C", "text": "Acoustic Neuroma", "correct": false}, {"label": "D", "text": "CSF Rhinorrhea", "correct": false}], "correct_answer": "A. Meniere's Disease", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Meniere's Disease Endolymphatic sac decompression is done as a conservative procedure in Ménière's disease.</p>\n<p><strong>Highyeild:</strong></p><p>Conservative procedures done for Ménière's include: Decompression of endolymphatic sac Endolymphatic shunt operation Sacculotomy (Fick's operation) Section of the vestibular nerve Ultrasonic destruction of vestibular labyrinth. These are used when vertigo is disabling, but hearing is still useful and needs to be preserved.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are an intern posted in the ENT department. Middle-aged patients come to you with complaints of episodes of vertigo and tinnitus. He was diagnosed to have Meniere’s disease. Common presenting manifestations of Meniere's disease are all except:", "options": [{"label": "A", "text": "Tinnitus", "correct": false}, {"label": "B", "text": "Vertigo", "correct": false}, {"label": "C", "text": "Sensorincural Deafness", "correct": false}, {"label": "D", "text": "Loss of Consciousness", "correct": true}], "correct_answer": "D. Loss of Consciousness", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Loss of Consciousness The patient is fully conscious and oriented during an attack of Meniere. Meniere being a disturbance of the peripheral vestibular system is therefore not characterised by any focal (such as diplopia, dysarthria, paresthesia, etc.) or generalised neurological symptoms like loss of consciousness, seizures, etc.</p>\n<p><strong>Highyeild:</strong></p><p>MENIERE’S DISEASE/ ENDOLYMPHATIC HYDROPS It is a disease of membranous Labyrinth Causes: Increase in production of Endolymph, Decrease in drainage or Both seen in Middle-Aged Males (30- 50yrs) Unilateral Presentation Fluctuating tinnitus Fluctuating hearing loss (SNHL) Episodic vertigo, Nystagmus, Nausea Special features of Meniere’s Hennebert’s sign : False positive Fistula sign. Tulio’s Phenomenon : Loud noise Produces vertigo/ Nystagmus. Diplacusis : The same word heard 2 times in different frequencies in the same ear. Recruitment.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option:A. Tinnitus is the presentation of Meniere’s disease. Option: B. Episodic vertigo can also be the presentation in Meniere’s disease. Option: C. Sensorineural hearing deafness is also seen in Meniere’s disease.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 60-year-old male presents with episodes of severe vertigo which lasts for 4 hours, associated with vomiting. O/E he is having right horizontal nystagmus and mild right-sided SN hearing loss during the episode. What is your diagnosis?", "options": [{"label": "A", "text": "Vertibro-Basilar Ischemic Attacks", "correct": false}, {"label": "B", "text": "Labyrinthitis", "correct": false}, {"label": "C", "text": "Meniere's Disease", "correct": true}, {"label": "D", "text": "Benign Paroxysmal Vertigo", "correct": false}], "correct_answer": "C. Meniere's Disease", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Meniere's Disease From the above history and examination findings it is clear that the patient is suffering from Meniere’s disease.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Vertebrobasilar (posterior) circulation constitutes the arterial supply to the brainstem, cerebellum, and occipital cortex. In its ischemAlong with vertigo, nausea and vomiting, the patient presents with loss of vision in one or both eyes, double vision, numbness or tingling in the hands or feet, slurred speech and changes in mental status. Option: B. In labyrinthitis the vertigo and hearing loss last for à few weeks and not for 4 hours. Option: D. In Benign paroxysmal vertigo there is only vertigo and no hearing loss</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following statements about Weber's test is true in conductive deafness?", "options": [{"label": "A", "text": "Sound lateralized to the normal ear", "correct": false}, {"label": "B", "text": "Sound lateralized to diseased ear", "correct": true}, {"label": "C", "text": "Heard with equal intensity in both ears", "correct": false}, {"label": "D", "text": "Inconclusive Test", "correct": false}], "correct_answer": "B. Sound lateralized to diseased ear", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Sound lateralized to diseased ear In the Weber test in conductive deafness, the sound is lateralized to the diseased ear.</p>\n<p><strong>Highyeild:</strong></p><p>WEBER TEST In normal Individuals - No lateralization of sound occurs as Bone conduction of both ears is normal and equal. In conductive deafness - Lateralization of sound occurs in the diseased ear In SNHL - lateralization of sound occurs in the better ear. It is a very sensitive test and even less than a 5 dB difference In 2 ears hearing level can be indicated.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In the Weber test sound is lateralized to a better ear in sensorineural hearing loss. Option: C. Sound is heard with equal intensity in both ears in normal individuals.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 27 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 6</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Nasal Polyps - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 6</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 6 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 33-year-old man was prescribed aspirin for post-MI prophylaxis. But he develops difficulty breathing, sneezing, and lacrimation whenever he takes the medication. He has a known family history of bronchial asthma. Which of the following is related to this condition?", "options": [{"label": "A", "text": "Ethmoidal Polyp", "correct": true}, {"label": "B", "text": "Angiofibroma", "correct": false}, {"label": "C", "text": "Nasopharyngeal Carcinoma", "correct": false}, {"label": "D", "text": "Nasal Glioma", "correct": false}], "correct_answer": "A. Ethmoidal Polyp", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ethmoidal Polyp The scenario suggests Samter's triad, where the patient will have aspirin intolerance , asthma, and ethmoidal polyps.</p>\n<p><strong>Highyeild:</strong></p><p>Other etiological factors of ethmoidal polyp: Chronic rhinosinusitis, especially non-allergic rhinitis with eosinophilia syndrome Allergic fungal sinusitis Kartagener syndrome consists of bronchiectasis, sinusitis, situs inversus and ciliary dyskinesis. Young syndrome - It consists of sinopulmonary disease and azoospermia. Churg-Strauss syndrome - Consists of asthma, fever, eosinophilia, vasculitis and granuloma. Asthma Cystic fibrosis</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. Angiofibroma has a history of profuse recurrent epistaxis. It is firm in consistency and easily bleeds on probing . Option C. The patient has no B symptoms, so nasopharyngeal carcinoma is ruled out. Option D. Glioma is a nipped-off portion of encephalocele during embryonic development. Most are extra nasal and present as firm subcutaneous swellings on the bridge, side of the nose or near the inner canthus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 43-year-old Patient with an ethmoidal polyp undergoes polypectomy and presents six months later with an ethmoid polyp; the correct treatment is:", "options": [{"label": "A", "text": "Fess", "correct": true}, {"label": "B", "text": "External Ethmoidectomy", "correct": false}, {"label": "C", "text": "Intranasal Polypectomy", "correct": false}, {"label": "D", "text": "Caldwell-Luc Procedure", "correct": false}], "correct_answer": "A. Fess", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Fess The surgical management of recurrent ethmoidal polyps is excision by FESS.</p>\n<p><strong>Highyeild:</strong></p><p>ADVANTAGES OF FESS Better visualisation & magnification Almost can be operated upon Almost can be treated with FESS FESS – for disease outside the nose and sinuses No external scar etc. Minimal tissue damage Precise surgery due to advanced instruments</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. External ethmoidectomy used to be done for recurrent ethmoidal polyps before the advent of FESS. Option C. Intranasal polypectomy, the avulsion of polyp using a násal snare, is no longer done. Option D. Caldwell-Luc procedure, a sub-labial approach for the maxillary sinus, was previously done for recurrent antrochoanal polyps. Nowadays, FESS is done for the same.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are sitting in the opd of the ENT department. A child was diagnosed to have a nasal polyp. The current treatment of choice for a large antrochoanal polyp in a 10-year-old is:", "options": [{"label": "A", "text": "Intranasal Polypectomy", "correct": false}, {"label": "B", "text": "Caldwell-Luc Operation", "correct": false}, {"label": "C", "text": "FESS", "correct": true}, {"label": "D", "text": "Lateral rhinotomy and excision", "correct": false}], "correct_answer": "C. FESS", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>FESS Antrochoanal polyps arise from the maxillary antrum and grow into the nasopharynx. Hence they are always large at presentation, and management is excision by FESS.</p>\n<p><strong>Highyeild:</strong></p><p>ADVANTAGES OF FESS Better visualisation & magnification Almost can be operated upon Almost can be treated with FESS FESS – for disease outside the nose and sinuses No external scar etc. Minimal tissue damage Precise surgery Due to advanced instruments</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Intranasal polypectomy, which is the avulsion of polyp using a násal snare, is no longer done. Option B. Caldwell-Luc procedure, a sub-labial approach for the maxillary sinus, was previously done for recurrent antrochoanal polyps. Nowadays, FESS is done for the same. Option D. Lateral rhinotomy and excision are not done for antrochoanal polyp.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 33-year-old female patient came to you in the OPD complaining of bilateral nasal obstruction, headache, sneezing, and discharge. On examination, multiple smooth pale glistening masses are present bilaterally that don't bleed on touch. Which of the following is false about the diagnosis?", "options": [{"label": "A", "text": "Allergy is an etiological factor", "correct": false}, {"label": "B", "text": "The site of origin is the lateral wall of the nose", "correct": false}, {"label": "C", "text": "FESS is the treatment of choice", "correct": false}, {"label": "D", "text": "The mass can grow posteriorly and occupy the choana", "correct": true}], "correct_answer": "D. The mass can grow posteriorly and occupy the choana", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>The mass can grow posteriorly and occupy the choana The given case scenario with multiple bilateral nasal masses in a middle-aged woman suggests an ethmoidal polyp that doesn't grow posteriorly to occupy choana.</p>\n<p><strong>Highyeild:</strong></p><p>Ethmoidal Polyps - Features Mnemonic Adult BMR Adult - It is seen in adults B = Bilateral M = Multiple R =-Recurrence is common</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Allergy is an etiological factor is a true statement. Option B. Multiple nasal polypi always arise from the lateral wall of the nose, usually from the middle meatus. Option C. FESS is the treatment of choice for ethmoidal polyp is also true.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are true about nasolabial cysts except:", "options": [{"label": "A", "text": "B/L in only 10% of cases", "correct": false}, {"label": "B", "text": "They present in adults", "correct": false}, {"label": "C", "text": "Derived from odontogenic epithelium", "correct": true}, {"label": "D", "text": "Strong female predilection", "correct": false}], "correct_answer": "C. Derived from odontogenic epithelium", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Derived from odontogenic epithelium Nasolabial cysts is a non odontogenic cyst.</p>\n<p><strong>Highyeild:</strong></p><p>Nasolabial Cysts/Nasoalveolar Cyst/Klestadt's Cyst It is a rare non-odontogenic cyst originating from epithelial entrapment in the fusion line between maxillary and median nasal elevation. Female >Male Bilateral in approximately 10% of all cases. Usually present in the 4th and 5th decades of life. It lies on the bone and causes an excavation. It is closely attached to the floor of the nose. It presents as a smooth and soft bulge in the lateral wall and floor of the vestibule anterior to the inferior turbinate. A large cyst obliterates the alar facial fold (nasolabial sulcus). MRI is the best investigation Treatment is by surgical excision using a sublabial approach.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Nasolabial cyst is B/L in only 10% of cases. Option B. Nasolabial cysts are usually present in the 4th-5th decade of life. So option B is a true statement. Option D. Nasolabial cysts have strong female predilection is also a true statement.</p>\n<p><strong>Extraedge:</strong></p><p>Globulomaxillary cysts arise at the junction of the primitive palate and palatine process in the alveolaolar process between the lateral incisor and canine teeth.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Identify the instrument shown in the Image used in ENT surgeries.", "options": [{"label": "A", "text": "Doyen’s mouth gag", "correct": false}, {"label": "B", "text": "Tonsil Holding Forceps", "correct": false}, {"label": "C", "text": "Negus artery forceps", "correct": false}, {"label": "D", "text": "Luc's Forceps", "correct": true}], "correct_answer": "D. Luc's Forceps", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004747437-QTDE058007IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Luc's Forceps Luc’s forceps are used in Caldwell–Luc operation (to remove mucosa), submucosal resection (SMR) operation (to remove bone or cartilage), polypectomy (to grasp and avulse polyp), and to take a biopsy from the nose or throat.</p>\n<p><strong>Highyeild:</strong></p><p>LUC’S FORCEPS</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Doyen’s mouth gag is used to keep the mouth open for intraoral surgery when retraction of the tongue is not required or desirable. Option B. Tonsil Holding Forceps used for holding the tonsil during tonsillectomy by dissection method. Option C. Negus artery forceps- Its tip is sharply curved. The forceps is used as replacement forceps to ligate the bleeding point.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 16 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 9</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Nasopharyngeal Carcinoma - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 9</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 9 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A patient with persistent cervical lymphadenopathy now came with spontaneous epistaxis. Nasal endoscopy showed a mass arising from the fossa of Rosen muller. All are true about the given condition except which of the following?", "options": [{"label": "A", "text": "Bimodal Age Distribution", "correct": false}, {"label": "B", "text": "EBV is implicated as the etiological agent", "correct": false}, {"label": "C", "text": "Squamous cell carcinoma is the most common type", "correct": false}, {"label": "D", "text": "Surgery is the mainstay of treatment", "correct": true}], "correct_answer": "D. Surgery is the mainstay of treatment", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Surgery is the mainstay of treatment Radiotherapy and Chemotherapy are the mainstays of treatment . Surgery is not the mainstay of treatment. Radical neck dissection is done if persistent positive nodes are present.</p>\n<p><strong>Highyeild:</strong></p><p>Nasopharyngeal Carcinoma It has bimodal age distribution, primarily seen in the 5th to 7th decade but may involve younger age groups. Aetiology: Genetic – Chinese are more prone Viral – EBV is commonly involved Environmental- Air pollution, tobacco, opium, nitrosamines Sex: Male 3 times more common than female Pathology: In various grades of its differentiation, squamous cell carcinomas, like transitional cell carcinoma and lymphoepithelioma, involve 85% of the cases. Treatment: Radiotherapy and Chemotherapy are the mainstays of treatment. Radical neck dissection is done if persistent positive nodes are present.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Bimodal Age Distribution is a true statement. Option: B. EBV is implicated as an etiological agent is also a true statement. Option: C. Squamous cell carcinoma is the most common type is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45 year male presents with conductive hearing loss, pain in the temporoparietal region with pain in the lower face and an immobile soft palate on the right side. The most probable diagnosis is:", "options": [{"label": "A", "text": "Costen’s Syndrome", "correct": false}, {"label": "B", "text": "Sluder's Neuralgia", "correct": false}, {"label": "C", "text": "Wallenberg’S Syndrome", "correct": false}, {"label": "D", "text": "Trotter's Syndrome", "correct": true}], "correct_answer": "D. Trotter's Syndrome", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Trotter's Syndrome The above features represent the trotter’s triad/syndrome.</p>\n<p><strong>Highyeild:</strong></p><p>Trotter (or Sinus of Morgagni) syndrome or triad It is seen in nasopharyngeal carcinoma, which spreads laterally to involve the sinus of Morgagni involving the mandibular nerve. It is characterised by the following: (a) Conductive hearing loss (due to eustachian tube obstruction). (b) Ipsilateral immobility of soft palate. (c) Neuralgic pain in the distribution of V3.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "FIBROUS dysplasia most commonly occurs in which sinus?", "options": [{"label": "A", "text": "Maxillary Sinus", "correct": true}, {"label": "B", "text": "Frontal Sinus", "correct": false}, {"label": "C", "text": "Ethmoid Sinus", "correct": false}, {"label": "D", "text": "Sphenoid Sinus", "correct": false}], "correct_answer": "A. Maxillary Sinus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Maxillary Sinus Fibrous dysplasia most commonly involves the maxillary sinus.</p>\n<p><strong>Highyeild:</strong></p><p>Fibrous Dysplasia It is a benign neoplasm where bone is replaced by fibrous tissue. It commonly involves the Maxillary sinus but may also involve the ethmoid and frontal. The patient seeks advice for disfigurement of the face, nasal obstruction and displacement of the eye. Treatment is surgical resculpturing of the involved bone to achieve a good cosmetic and functional result. Fibrous dysplasia of maxilla in a 13-year-old girl. (A) As seen externally. (B) After retraction of the lip.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Bilateral proptosis with 6th nerve palsy is seen in:", "options": [{"label": "A", "text": "Cavernous Sinus Thrombosis", "correct": true}, {"label": "B", "text": "Meningitis", "correct": false}, {"label": "C", "text": "Hydrocephalus", "correct": false}, {"label": "D", "text": "Orbital Cellulitis", "correct": false}], "correct_answer": "A. Cavernous Sinus Thrombosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cavernous Sinus Thrombosis Bilateral proptosis with 6th nerve palsy is seen in cavernous sinus thrombosis.</p>\n<p><strong>Highyeild:</strong></p><p>CAVERNOUS SINUS THROMBOSIS Etiology- Infection of paranasal sinuses Clinical features Chills and rigours, eyelids getting swollen, chemosis, and eyeball proptosis. Cranial nerves 3, 4, and 6 are involved and cause ophthalmoplegia. The pupil becomes dilated and fixed. The optic disc shows congestion and oedema with a diminution of vision. Treatment – IV antibiotics and drainage of the infected sinus.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. In meningitis, neck rigidity will also be present. Option: C. In Hydrocephalus, these features are not seen. Option: D. In orbital cellulitis, Clinical features will include oedema of lids, exophthalmos, conjunctiva chemosis and restricted eyeball Vision is affected, causing partial or total loss, which is sometimes permanent.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient presented with atrophic dry nasal mucosa, extensive crustations and a woody hard external nose. What is the most likely diagnosis?", "options": [{"label": "A", "text": "Rhinosporidiosis", "correct": false}, {"label": "B", "text": "Rhinoscleroma", "correct": true}, {"label": "C", "text": "Atrophic Rhinitis", "correct": false}, {"label": "D", "text": "Carcinoma of the Nose", "correct": false}], "correct_answer": "B. Rhinoscleroma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rhinoscleroma Atrophic dry nasal mucosa , extensive crustations and a woody hard external nose are seen in Rhinoscleroma.</p>\n<p><strong>Highyeild:</strong></p><p>RHINOSCLEROMA Caused by - Klebsiella rhinoscleromatis Clinical features Atrophic stage: Foul smelling purulent nasal discharge and crusting. Granulomatous stage : Granulomatous nodules form in nasal mucosa. There is subdermal infiltration into the lower part of the external nose and upper lip, giving a Woody feel. Cicatricial stage: Stenosis of nares and distortion of the upper lip. Rhinoscleroma nose.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In the nose, the rhinoscleroma is a leafy, polypoidal mass, pink to purple and attached to the nasal septum or lateral wall. Sometimes, it extends into the nasopharynx and may hang behind the soft palate. The mass is very vascular and bleeds easily on the touch . Option: C. Atrophic rhinitis is a chronic inflammation of the nose characterised by atrophy of nasal mucosa and turbinate bones. The nasal cavities are roomy and full of foul-smelling crusts. Option: D. In carcinoma of nose B, symptoms will also be present.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Trotter's triad seen in?", "options": [{"label": "A", "text": "Nasopharyngeal Angiofibroma", "correct": false}, {"label": "B", "text": "Nasopharyngeal Carcinoma", "correct": true}, {"label": "C", "text": "Vestibular Schwannoma", "correct": false}, {"label": "D", "text": "Parapharyngeal Tumour", "correct": false}], "correct_answer": "B. Nasopharyngeal Carcinoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Nasopharyngeal Carcinoma Trotter’s syndrome is a cluster of symptoms associated with certain types of advanced nasopharyngeal carcinoma.</p>\n<p><strong>Highyeild:</strong></p><p>Trotter (or Sinus of Morgagni) syndrome or triad It is seen in nasopharyngeal carcinoma, which spreads laterally to involve the sinus of Morgagni involving the mandibular nerve. It is characterised by the following: (a) Conductive hearing loss (due to eustachian tube obstruction). (b) Ipsilateral immobility of soft palate. (c) Neuralgic pain in the distribution of V3.</p>\n<p><strong>Extraedge:</strong></p><p>They present with hoarseness and if large, dyspnoea as well. Treatment is voice rest and endoscopic removal of the granuloma.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient with persistent cervical lymphadenopathy came with spontaneous epistaxis. Nasal endoscopy shows mass arising from the fossa of rossenmuller. Radiotherapy is a treatment of choice for which of the following condition?", "options": [{"label": "A", "text": "Nasopharyngeal carcinoma T3 N1", "correct": true}, {"label": "B", "text": "Supraglottic carcinoma T3 N0", "correct": false}, {"label": "C", "text": "Glottic carcinoma T3 N0", "correct": false}, {"label": "D", "text": "Subglottic carcinoma T3 N0", "correct": false}], "correct_answer": "A. Nasopharyngeal carcinoma T3 N1", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Nasopharyngeal carcinoma T3 N1 The best management of nasopharyngeal carcinoma in all stages is radiotherapy.</p>\n<p><strong>Highyeild:</strong></p><p>TNM STAGING OF NASOPHARYNGEAL CANCER Primary tumour Τ 1 Tumour confined to the nasopharynx T 2 Tumour extends to soft tissues of oropharynx and/or nasal fossa T 2 a without parapharyngeal extension T 2 b with parapharyngeal extension T 3 Tumour invades bony structures and/or paranasal sinuses T 4 Tumour with intracranial extension and/or involvement of cranial nerves, infratemporal fossa, hypopharynx or orbit or masticator space Regional The distribution and the prognostic impact of regional lymph node spread from nasopharynx cancer, particularly of the undifferentiated type, is different from that of other head and neck mucosal cancers and justifies use of a different N classification scheme N x Regional lymph nodes cannot be assessed N 0 No regional lymph node metastasis N 1 Unilateral metastasis in lymph node(s), 6 cm or less in greatest dimension, above the supraclavicular fossa N 2 Bilateral metastasis in lymph nodes, 6 cm or less in greatest dimension, above the supraclavicular fossa. N 3 Metastasis in a lymph node(s) Nзa Greater than 6 cm in dimension Nзb In the supraclavicular fossa Distant metastasis My Distant metastasis cannot be assessed Mo No distant metastasis M₁ Distant metastasis</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- The rest of the tumours mentioned are laryngeal carcinoma stage 3, where treatment is concurrent chemoradiation.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is not true about nasopharyngeal carcinoma?", "options": [{"label": "A", "text": "Bimodal Age Distribution", "correct": false}, {"label": "B", "text": "EBV is implicated as an etiological agent", "correct": false}, {"label": "C", "text": "Squamous cell carcinoma is the most common.", "correct": false}, {"label": "D", "text": "Naso pharyngectomy and lymph node dissection are the mainstay of treatment.", "correct": true}], "correct_answer": "D. Naso pharyngectomy and lymph node dissection are the mainstay of treatment.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Naso pharyngectomy and lymph node dissection are the mainstay of treatment. Radiotherapy is the treatment of choice, as it is not only the primary tumour but also the regional nodal metastases exquisitely sensitive to radiotherapy. Nasopharyngeal carcinoma is also highly radiocurable , with disease-free survival at three years.</p>\n<p><strong>Highyeild:</strong></p><p>NASOPHARYNGEAL CARCINOMA</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. It is mainly seen in the fifth to seventh decades but may involve younger age groups. So Bimodal age distribution is seen. Option: B. EBV is implicated as an etiological agent is a true statement. Option: C. Squamous cell carcinoma is the most common type is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Most common malignant tumour of the nasal cavity is:", "options": [{"label": "A", "text": "Squamous Cell Carcinoma", "correct": true}, {"label": "B", "text": "Basal Cell Carcinoma", "correct": false}, {"label": "C", "text": "Malignant Melanoma", "correct": false}, {"label": "D", "text": "Neuroblastoma", "correct": false}], "correct_answer": "A. Squamous Cell Carcinoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Squamous Cell Carcinoma Squamous cell variety is the most common , seen in about 80 per cent of cases. Rest may be adenoid cystic carcinoma or adenocarcinoma.</p>\n<p><strong>Highyeild:</strong></p><p>TUMOURS OF NASAL CAVITY Benign Malignant ● Squamous papilloma ● Inverted papilloma ● Pleomorphic adenoma ● Schwannoma ● Meningioma ● Haemangioma ● Chondroma ● Angiofibroma ● Encephalocele ● Glioma ● Dermoid ● Carcinoma ○ Squamous cell carcinoma ○ Adenocarcinoma ● Malignant melanoma ● Esthesioneuroblastoma ● Haemangiopericytoma ● Lymphoma ● Solitary plasmacytoma ● Various types of sarcoma</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 19 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 5</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Nasopharynx: Anatomy & Diseases - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 5</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 5 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 50-year-old male presented to ENT Opd with Right nasal obstruction with epistaxis for the last six months only. Biopsy suggestive of tumor showing stromal infiltration. CT shows no bony erosion or intraorbital extension. The likely diagnosis is-", "options": [{"label": "A", "text": "Nasal Malignancy", "correct": false}, {"label": "B", "text": "Ringertz Tumour", "correct": true}, {"label": "C", "text": "Nasopharyngeal CA", "correct": false}, {"label": "D", "text": "Angiofibroma", "correct": false}], "correct_answer": "B. Ringertz Tumour", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ringertz Tumour The above history and biopsy findings indicate an inverted papilloma/Ringertz tumour.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Surgical Landmarks For Identification Of Facial Nerve In Mastoid Surgery Is All Except:", "options": [{"label": "A", "text": "Eustachian Tube", "correct": true}, {"label": "B", "text": "Between the oval window and horizontal semicircular canal", "correct": false}, {"label": "C", "text": "Medial to short process of the incus", "correct": false}, {"label": "D", "text": "Behind The Pyramid", "correct": false}], "correct_answer": "A. Eustachian Tube", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Eustachian Tube The eustachian tube is not the surgical landmark for the identification of facial nerves in mastoid surgery.</p>\n<p><strong>Highyeild:</strong></p><p>Surgical landmarks of the facial nerve in mastoid surgery : Processus Cochleariformis Oval window and horizontal canal The short process of the incus Pyramid Tympanomastoid suture Digastric ridge</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. The facial nerve runs above the oval window (stapes) and below the horizontal canal. Option C. Facial nerve lies medial to the short incus process at the aditus level. Option D. Nerve runs behind the pyramid and the posterior tympanic sulcus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 10-year-old child presents to the OPD with difficulty swallowing and pain in the throat. On examination, you can visualise an enlarged Waldeyer's group. You ask the 3rd year students in the OPD to draw the diagram of the Waldeyer's Ring and label the parts correctly below:", "options": [{"label": "A", "text": "1- Tubal tonsil. 2- Palatine tonsil. 3-Lingual tonsil", "correct": true}, {"label": "B", "text": "1- Tubal tonsil. 2- Lingual tonsil. 3-Palatine tonsil", "correct": false}, {"label": "C", "text": "1- Lingual tonsil. 2- Palatine tonsil. 3-Tubal tonsil", "correct": false}, {"label": "D", "text": "1- Palatine tonsil. 2- Tubal tonsil. 3-Lingual tonsil", "correct": false}], "correct_answer": "A. 1- Tubal tonsil. 2- Palatine tonsil. 3-Lingual tonsil", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004831486-QTDE059003IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>1- Tubal tonsil. 2- Palatine tonsil. 3-Lingual tonsil 1- Tubal tonsil. 2- Palatine tonsil. 3- Lingual tonsil is correct</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A child presents to the ent opd with an elongated face with a dull expression, open mouth, prominent and crowded upper teeth and hitched up upper lip. The image is as below. Which of the following statements is incorrect?", "options": [{"label": "A", "text": "Recurrent rhinitis, sinusitis or chronic tonsillitis attacks may cause chronic adenoid infection and hyperplasia.", "correct": false}, {"label": "B", "text": "It is commonly known as Tiger facies due to mouth breathing.", "correct": true}, {"label": "C", "text": "Nasal obstruction is the most typical symptom. This leads to mouth breathing.", "correct": false}, {"label": "D", "text": "Adenoids form an important cause of otitis media with effusion in children.", "correct": false}], "correct_answer": "B. It is commonly known as Tiger facies due to mouth breathing.", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004831562-QTDE059004IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>It is commonly known as Tiger facies due to mouth breathing. The above image shows frog facies which are seen in juvenile nasopharyngeal angiofibroma. The case and image above are of adenoid facies.</p>\n<p><strong>Highyeild:</strong></p><p>Adenoid Facies Chronic nasal obstruction and mouth breathing lead to a characteristic facial appearance called adenoid facies. The child has an elongated face with a dull expression, open mouth, prominent and crowded upper teeth and hitched-up upper lip. The nose gives a pinched-in appearance due to disuse atrophy of alaenasi. The hard palate, in these cases, is highly arched as the moulding action of the tongue on the palate is lost.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Adenoids are subject to physiological enlargement in childhood. Certain children tend to generalise lymphoid hyperplasia in which adenoids also take part. Recurrent rhinitis attacks, sinusitis or chronic tonsillitis, may cause chronic adenoid infection and hyperplasia. Allergy of the upper respiratory tract may also contribute to the enlargement of adenoids. Option C. Nasal obstruction is the commonest symptom. This leads to mouth breathing. Nasal obstruction also interferes with feeding or suckling in a child. As respiration and feeding cannot occur simultaneously, a child with adenoid enlargement fails to thrive. Option D. Otitis media with effusion. Adenoids form an important cause of otitis media with effusion in children. Adenoids' waxing and waning size cause intermittent eustachian tube obstruction with fluctuating hearing loss. Impedance audiometry helps to identify the condition.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 10-year-old school-going child presents to your OPD with typical adenoid facies and related symptoms. Which of the following statements is incorrect with respect to the adenoid tissue:", "options": [{"label": "A", "text": "Adenoid tissue is present at birth", "correct": false}, {"label": "B", "text": "Adenoid shows physiological enlargement up to 18 years of age", "correct": true}, {"label": "C", "text": "Adenoid tends to atrophy by puberty", "correct": false}, {"label": "D", "text": "Adenoids almost disappear entirely by the age of 20 years", "correct": false}], "correct_answer": "B. Adenoid shows physiological enlargement up to 18 years of age", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Adenoid shows physiological enlargement up to 18 years of age Adenoids show physiological enlargement up to the age of 6 years. B is incorrect. A, C, and D are correct.</p>\n<p><strong>Highyeild:</strong></p><p>ADENOIDS Adenoid tissue is present at birth, shows physiological enlargement up to the age of 6 years, and then tends to atrophy at puberty and almost completely disappears by age 20. The nasopharyngeal tonsil, commonly called “adenoids”, is situated at the junction of the roof and posterior wall of the nasopharynx. It is composed of vertical ridges of lymphoid tissue separated by deep clefts.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Adenoid tissue is present at birth is a true statement. Option C. Adenoid tends to atrophy by puberty is also a true statement. Option D. Adenoids almost disappear entirely by the age of 20 years is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 15 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 6</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Neck Dissections - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 6</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 6 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Isthmus of the thyroid gland corresponds to which of the following tracheal rings?", "options": [{"label": "A", "text": "1-3", "correct": false}, {"label": "B", "text": "2-4", "correct": true}, {"label": "C", "text": "4-6", "correct": false}, {"label": "D", "text": "6-8", "correct": false}], "correct_answer": "B. 2-4", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>2-4 The Isthmus of the thyroid gland lies in front of the second, third, and fourth tracheal rings.</p>\n<p><strong>Highyeild:</strong></p><p>THYROID GLAND It is covered in front by skin and fascia, and by sternothyroid and sternohyoid muscles, and anterior jugular veins. The Isthmus of the thyroid gland lies in front of the second, third, and fourth tracheal rings. The upper end of each thyroid lobe lies opposite the C5 vertebrae. The lower end of the lobe lies at the level of the 5th or 6th tracheal ring corresponding to the T1 vertebrae.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Thumb sign in lateral X-ray of neck seen in:", "options": [{"label": "A", "text": "Epiglottitis", "correct": true}, {"label": "B", "text": "Internal Hemorrhage", "correct": false}, {"label": "C", "text": "Saccular Cyst", "correct": false}, {"label": "D", "text": "CA Epiglottis", "correct": false}], "correct_answer": "A. Epiglottitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Epiglottitis Thumb sign in lateral X-ray of neck seen in Epiglottitis</p>\n<p><strong>Highyeild:</strong></p><p>EPIGLOTTITIS It is a severe condition and affects children of 2–7 years of age but can also affect adults. influenza B is the most common organism responsible for this condition in children.</p>\n<p><strong>Extraedge:</strong></p><p>Clinical Presentation High-grade fever TRIPOD POSITION - Sit on the bed, bends forward, and put your hand forward for support. Dyspnea /Stridor. Dysphagia/Odynophagia - Difficulty/ Pain in swallowing Examination : Laryngoscopy contraindicated or dangerous - If done, it should be under proper precaution to avoid laryngeal spasms. On Laryngoscopy- Cherry red epiglottis/ rising sun sign. X-Ray Lateral view of the soft tissue of the neck - THUMB SKIN Steeple sign, e., narrowing of the subglottic region, is seen in chest X-ray patients of laryngotracheobronchitis (i.e., croup).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are examining the neck nodes of a patient and notice that level VI group of lymph nodes is inflamed. These include all of the following:", "options": [{"label": "A", "text": "Prelaryngeal", "correct": false}, {"label": "B", "text": "Pretracheal", "correct": false}, {"label": "C", "text": "Paratracheal", "correct": false}, {"label": "D", "text": "Submental", "correct": true}], "correct_answer": "D. Submental", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Submental The submental group of lymph nodes belongs to the level I division of lymph nodes, not level VI.</p>\n<p><strong>Highyeild:</strong></p><p>LEVELS OF LYMPH NODES Level I • Submental (IA) Submandibular (IB) Level II Upper jugular Anterior to CNXI (IIA) Posterior to CNXI (IIB) Level III Mid jugular Level IV Lower jugular Level V Posterior triangle group (spinal accessory and transverse cervical chains) Above the level of intermediate tendon of omohyoid (VA) Below the level of intermediate tendon of omohyoid (VB) Level VI • Prelaryngeal Pretracheal Paratracheal Level VII Nodes of upper mediastinum</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. The laryngeal belongs to the level VI division of lymph nodes. Option: B. Pretracheal also belongs to the level VI division of lymph nodes. Option: C. Paratracheal also belongs to the level VI division of lymph nodes.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient in the ENT ward wants to know if radical neck dissection would be a treatment option for his case. Given below are all contraindications to radical neck dissection, except:", "options": [{"label": "A", "text": "Untreatable Primary Cancer.", "correct": false}, {"label": "B", "text": "No Metastases.", "correct": true}, {"label": "C", "text": "Inoperable neck nodes when they are fixed to important structures.", "correct": false}, {"label": "D", "text": "Medical illness that makes the patient unfit for major surgery", "correct": false}], "correct_answer": "B. No Metastases.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>No Metastases. No metastasis is not a contraindication . Metastasis is a contraindication for radical neck dissection.</p>\n<p><strong>Highyeild:</strong></p><p>RADICAL NECK DISSECTION Crile described the operation in 1906 En -bloc clearance of fibrofatty tissue from one side of the neck, including the lymph nodes from levels I - V and lymph nodes that surround the tail of the parotid gland, With the removal of 3 essential structures: The spinal accessory nerve The internal jugular The Sternocleidomastoid muscle Contraindications to radical neck dissection include: Distant metastases. Untreatable primary cancer. Inoperable neck nodes when are fixed to important structures. Medical illness which makes the patient unfit for major surgery</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Untreated primary cancer is a contraindication to radical neck-neck dissection. Option: C. Inoperable neck nodes are also a contraindication to radical neck dissection when fixed to important structures. Option: D. Medical illness which makes the patient unfit for major surgery is also a contraindication to radical neck dissection.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are currently posted in the ENT department and observe a radical neck dissection in the OT. The professor quizzes you about the structures saved in Radical neck dissection. All the given options below are saved, except:", "options": [{"label": "A", "text": "Phrenic Nerve", "correct": false}, {"label": "B", "text": "Vagus Nerve", "correct": false}, {"label": "C", "text": "Spinal Accessory Nerve", "correct": true}, {"label": "D", "text": "Hypoglossal Nerve", "correct": false}], "correct_answer": "C. Spinal Accessory Nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Spinal Accessory Nerve Radical neck dissection removes the spinal accessory nerve (not preserved).</p>\n<p><strong>Highyeild:</strong></p><p>Radical Neck Dissection Crile described the operation in 1906 En -bloc clearance of fibrofatty tissue from one side of the neck, including the lymph nodes from levels I - V and lymph nodes that surround the tail of the parotid gland, With the removal of 3 essential structures: The spinal accessory nerve The internal jugular The Sternocleidomastoid muscle</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Phrenic Nerve is saved in radical neck dissection. Option: B. Vagus Nerve is saved in radical neck dissection. Option: D. Hypoglossal Nerve is saved in radical neck dissection.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Selective neck dissection of the anterior compartment involves the removal of the following nodes:", "options": [{"label": "A", "text": "Pretracheal", "correct": false}, {"label": "B", "text": "Paratracheal", "correct": false}, {"label": "C", "text": "Postauricular", "correct": true}, {"label": "D", "text": "Prelaryngeal", "correct": false}], "correct_answer": "C. Postauricular", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Postauricular Post auricular lymph nodes are not removed in selective neck dissection.</p>\n<p><strong>Highyeild:</strong></p><p>Selective Neck Dissection It consists of preserving one or more lymph node groups and all three non lymphatic structures, i.e., spinal accessory, sternocleidomastoid muscle, and internal jugular vein. Supraomohyoid or anterolateral . Removes levels I to III, usually done in oral cavity cancer. Lateral . Removes levels II to IV. Removes levels of II to V with postauricular and occipital nodes. Anterior compartment . Removes nodes at level VI, i.e., paratracheal, paratracheal, and laryngeal.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Pretracheal lymph nodes are also removed in selective neck dissection. Option: B. Paratracheal lymph nodes are also removed in selective neck dissection. Option: D. Prelaryngeal lymph nodes are also removed in selective neck dissection.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 16 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 6</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Neck Mass & Thyroid Swellings - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 6</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 6 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Which of the following structures marked in the diagram is a branch of the internal carotid artery?", "options": [{"label": "A", "text": "A", "correct": true}, {"label": "B", "text": "C", "correct": false}, {"label": "C", "text": "D", "correct": false}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "A. A", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004790017-QTDE060001IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>A The blood vessel marked A is the anterior ethmoidal artery . And it is a branch of the ophthalmic artery, a branch of the internal carotid artery. So the correct answer is option A.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option B. C is the greater palatine artery which is a branch of the maxillary artery that comes from the external carotid artery. Option C. D is the sphenopalatine artery which is a branch of maxillary artery which comes from the external carotid artery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient presents to your ENT OPD with neck swelling. On examination, you deduce that it’s a midline neck swelling. All of the given options below are midline neck swellings, except:", "options": [{"label": "A", "text": "Ludwig’s Angina", "correct": false}, {"label": "B", "text": "Thyroglossal Duct Cyst", "correct": false}, {"label": "C", "text": "Cystic Hygroma", "correct": true}, {"label": "D", "text": "Suprasternal Dermoid", "correct": false}], "correct_answer": "C. Cystic Hygroma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cystic Hygroma Cystic hygroma is a lateral neck swelling belonging to the supraclavicular triangle , not a midline neck swelling.</p>\n<p><strong>Highyeild:</strong></p><p>Midline Neck Swellings</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Ludwig’s Angina is a midline neck swelling. Option B. Thyroglossal Duct Cyst is a midline neck swelling. Option D. Suprasternal Dermoid is a midline neck swelling.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An elderly male presents to your OPD with neck swelling. The intern examines the patient and says it’s a lateral neck swelling. All of the given options below are lateral neck swellings, except:", "options": [{"label": "A", "text": "Laryngocele", "correct": false}, {"label": "B", "text": "Plunging Ranula", "correct": false}, {"label": "C", "text": "Cystic Hygroma", "correct": false}, {"label": "D", "text": "Ludwig’S Angina", "correct": true}], "correct_answer": "D. Ludwig’S Angina", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ludwig’S Angina Ludwig’s angina is a midline neck swelling , not a lateral neck swelling.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Laryngocele is lateral swelling in the neck. Option B. Plunging Ranula is also a lateral swelling in the neck. Option C. Cystic Hygroma is also a lateral swelling in the neck.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A young child is brought to your hospital with a neck mass. The parents are concerned. You explain to the parents that it’s a thyroglossal duct cyst. Which of the following statements are true? It is a cystic lateral neck swelling It increases in size with upper respiratory tract infection. It doesn’t move with tongue protrusion Rarely, carcinoma develops in the cyst Treatment is Sistrunks operation Treatment is simple excision of the cyst", "options": [{"label": "A", "text": "II, III, VI", "correct": false}, {"label": "B", "text": "I, IV, V", "correct": true}, {"label": "C", "text": "I, III, V", "correct": false}, {"label": "D", "text": "I, II, IV, VI", "correct": false}], "correct_answer": "B. I, IV, V", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>I, IV, V The correct answer is option B. Statement I- Usually affects young children but can occur at any age. It is usually rounded with a diameter of 2–4 cm. It increases in size with upper respiratory tract infection. Statement IV- Thyroglossal cysts can occur anywhere in the thyroid duct. It may contain the only functioning thyroid tissue in the body. Rarely carcinoma develops in the cyst. Statement V- Treatment is complete surgical excision, including the body of hyoid bone and core of tongue tissue around the tract in the suprahyoid tongue base to the foramen caecum (Sistrunk’s operation). Statement I- Thyroglossal duct cyst presents as a cystic midline swelling. Statement VI - Simple excision of the cyst without removal of its tract leads to recurrence. Statement III- Sometimes it presents as a draining sinus if it has burst due to infection or has been surgically drained. Because of the attachment of the thyroglossal duct to the foramen caecum at the base of the tongue, it moves with tongue protrusion. So out of the given option combination, option B is the correct answer.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A female of 45 years of age presents to your OPD with a neck mass in the Anterior Triangle of the neck. It presents as a painless swelling that is pulsatile. A bruit can be heard with a stethoscope. It moves from side to side but not vertically. Which of the following is a probable diagnosis?", "options": [{"label": "A", "text": "Subclavian Aneurysm", "correct": false}, {"label": "B", "text": "Cystic Hygroma", "correct": false}, {"label": "C", "text": "Cervical RIB", "correct": false}, {"label": "D", "text": "Carotid Body Tumour", "correct": true}], "correct_answer": "D. Carotid Body Tumour", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Carotid Body Tumour From the above history and examination findings correct answer is carotid body tumour.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Subclavian Aneurysm is present in a supraclavicular triangle, not an anterior triangle. Option B. Cystic Hygroma is present in the supraclavicular triangle, not the anterior triangle. Option C. Cervical rib is also present in the supraclavicular triangle, not the anterior triangle.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 30-year-old man presents to your OPD with multiple nodular swellings in the neck, as shown in the image below. FNAC reveals a granulomatous lesion, and acid-fast bacilli (AFB) can be demonstrated. Which of the following statements is not true regarding the patient's condition:", "options": [{"label": "A", "text": "Tuberculosis has become more common due to AIDS", "correct": false}, {"label": "B", "text": "Tubercular abscess may form when node(s) caseate.", "correct": false}, {"label": "C", "text": "Treatment consists of 2 years of ATT", "correct": true}, {"label": "D", "text": "Surgical excision of lymph node mass or abscess is occasionally required when drug treatment fails.", "correct": false}], "correct_answer": "C. Treatment consists of 2 years of ATT", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004790267-QTDE060007IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Treatment consists of 2 years of ATT As explained below, treatment consists of 6 months of ATT, not 2 years. Treatment consists of an initial two months course of four drugs (rifampicin, isoniazid, pyrazinamide and ethambutol) followed by four months course of rifampicin and isoniazid.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Tuberculosis is also becoming more common due to AIDS is a true statement. Option B. Tubercular abscess may form when nodes caseate is also a true statement. Option D. Nodes may initially increase size during treatment before they finally subside. Surgical excision of lymph node mass or abscess is occasionally required when drug treatment fails.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 16 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 6</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Obstructive Sleep Apnea Syndrome - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 6</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 6 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "In a patient with obstructive sleep apnoea, the methods of non-surgical management have failed to bring any relief. So, the patient is finally opting for a surgical treatment route. All of the below are surgical treatment options for OSA, except:", "options": [{"label": "A", "text": "Pharyngoplasty", "correct": false}, {"label": "B", "text": "Laryngoplasty", "correct": true}, {"label": "C", "text": "Mandibular Osteotomy", "correct": false}, {"label": "D", "text": "Maxillomandibular Osteotomy", "correct": false}], "correct_answer": "B. Laryngoplasty", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Laryngoplasty Laryngoplasty is not a treatment option for obstructive sleep apnea.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Uvulopalatopharyngoplasty is a surgical procedure for managing obstructive sleep apnea. Option C. Mandibular osteotomy with genioglossus advancement may also be the option in managing obstructive sleep apnea. Option D. Maxillomandibular osteotomy may also be the option for treating obstructive sleep apnea.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In a clinical trial studying the effects of a new drug for OSA (obstructive sleep apnoea), you assess the eligibility of patients based on a scale/score for daytime sleepiness, which is:", "options": [{"label": "A", "text": "Ransons Score", "correct": false}, {"label": "B", "text": "Epworth Score", "correct": true}, {"label": "C", "text": "Alvarado Score", "correct": false}, {"label": "D", "text": "Curb-65 Score", "correct": false}], "correct_answer": "B. Epworth Score", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Epworth Score Epworth Sleepiness Scale (ESS) score is used to assess daytime sleepiness.</p>\n<p><strong>Highyeild:</strong></p><p>Epworth Sleepiness Scale Situation Score (0-3) ● Sitting and reading ● Watching TV ● Sitting inactive in a public place (e.g. theatre or in a meeting) ● Being a passenger in a car for 1 h without break ● Lying down to rest in the afternoon when circumstances permit ● Sitting and talking to someone ● Sitting quietly after a lunch without alcohol ● Sitting in a car while stopped in traffic for a few minutes 0 = never dozing off; 1 = slight chance of dozing off; 2 = moderate chance of dozing off; 3 = high chance of dozing off.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Ranson score is for acute pancreatitis O ption C. Alvarado's score is for acute appendicitis Option D. CURB-65 score, is for community-acquired pneumonia</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You have to give a lecture on the consequences of Obstructive sleep apnoea (OSA), and all of the following are correct, except:", "options": [{"label": "A", "text": "Decreased Libido", "correct": false}, {"label": "B", "text": "Hypotension", "correct": true}, {"label": "C", "text": "Traffic Accidents", "correct": false}, {"label": "D", "text": "Angina Attacks", "correct": false}], "correct_answer": "B. Hypotension", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Hypotension Obstructive sleep apnea leads to hypertension, not hypotension .</p>\n<p><strong>Highyeild:</strong></p><p>Consequences Of Obstructive Sleep Apnoea Congestive heart failure/cor pulmonale Polycythaemia and hypertension Atrial and ventricular arrhythmias and left heart failure Decreased libido Snoring spouse syndrome Loss of memory Traffic accidents Attacks of angina</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Decreased Libido can be a complication of obstructive sleep apnea. Option C. Traffic accidents can be a complication of obstructive sleep apnea as the person will be sleepy the whole day. Option D. Angina attacks can also be a complication of obstructive sleep apnea.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You notice a rise in the number of cases of sleep apnoea. So to create awareness, the ENT department asks you to explain to the patients at the health camp. You are telling them about the risk factors for sleep apnoea. All are true, except:", "options": [{"label": "A", "text": "Male Gender", "correct": false}, {"label": "B", "text": "Obesity", "correct": false}, {"label": "C", "text": "Age above 40 years", "correct": false}, {"label": "D", "text": "Drug Abuse", "correct": true}], "correct_answer": "D. Drug Abuse", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Drug Abuse Drug abuse is not a risk factor for OSA.</p>\n<p><strong>Highyeild:</strong></p><p>Risk factors for OSA include : Male gender. Obesity. Age above 40 years.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Male Gender is a risk factor for obstructive sleep apnea. Option B. Obesity is also a risk factor for obstructive sleep apnea. Option C. Age above 40 is also a risk factor for obstructive sleep apnea.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A couple visits a physician since the husband has episodes where he can’t breathe at night. You suspect sleep apnoea. The gold standard for diagnosis of sleep apnoea is:", "options": [{"label": "A", "text": "Electroencephalography", "correct": false}, {"label": "B", "text": "Electrooculography", "correct": false}, {"label": "C", "text": "Polysomnography", "correct": true}, {"label": "D", "text": "Electromyography", "correct": false}], "correct_answer": "C. Polysomnography", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Polysomnography Polysomnography is the gold standard for the diagnosis of sleep apnoea.</p>\n<p><strong>Highyeild:</strong></p><p>POLYSOMNOGRAPHY Polysomnography is the gold standard for the diagnosis of sleep apnoea, and it includes various parameters such as EEG (electroencephalography)—to determine whether it is non-REM or REM sleep and stages of non-REM sleep. ECG (electrocardiography)—for heart rate and rhythm. EOM (electrooculogram)—for rolling eye movements. EMG (electromyography)—recorded from submental and tibialis anterior muscle. Pulse oximetry—to assess blood oxygen saturation to know the lowest SaO2 during sleep. Nasal and oral airflow—for episodes of apnoea and hypopnoea. Sleep position—helps to know whether apnoea/hypopnoea episodes occur in a supine or lateral recumbent position. Blood pressure. Oesophageal pressure.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Electroencephalography alone is not the diagnostic test for obstructive sleep apnea. Option B. Electrooculography alone is not the diagnostic test for obstructive sleep apnea. Option D. Electromyography alone is not the diagnostic test for obstructive sleep apnea.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient with obstructive sleep apnoea seeking treatment is advised non-surgical management first. Which of the following statements with respect to it is incorrect:", "options": [{"label": "A", "text": "Use of alcohol in the evening aggravates OSA", "correct": false}, {"label": "B", "text": "Intra-oral devices like MAD and TRD are used", "correct": false}, {"label": "C", "text": "Patient should sleep in a prone position, as a supine position may cause obstructive apnoea", "correct": true}, {"label": "D", "text": "Cpap Or Bipap may also be used for OSA.", "correct": false}], "correct_answer": "C. Patient should sleep in a prone position, as a supine position may cause obstructive apnoea", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Patient should sleep in a prone position, as a supine position may cause obstructive apnoea The patient should sleep on the side, not prone or supine.</p>\n<p><strong>Highyeild:</strong></p><p>Treatment of OSA (Nonsurgical) Positional therapy - The patient should sleep on the side, as a supine position may cause obstructive apnoea. A rubber ball can be fixed to the back of the shirt to prevent adopting a supine position. Lifestyle change. Those with mild disease and minimal symptoms can be treated with weight loss and dietary changes, but those with cor pulmonale due to severe OSA may require surgery. The use of alcohol in the evening aggravates OSA. Sedatives/hypnotics taken at night also have the same effect. Smoking should be avoided. Reduction of weight is helpful. Intraoral devices. They alter the position of the mandible or tongue to open the retropalatal airway and relieve snoring and sleep apnoea. A mandible advancement device (MAD) keeps the mandible forward, while a tongue-retaining device (TRD) keeps the tongue in the anterior position during sleep. They help improve or abolish snoring. MAD is also helpful in retrognathic patients. Continuous positive airway pressure (CPAP). It provides a pneumatic splint to the airway and increases its calibre. When CPAP is not tolerated, a BiPAP (bilevel positive airway pressure) device is used.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Use of alcohol in the evening aggravates OSA is a true statement. Option B. Intra-oral devices like MAD and TRD are used is also a true statement. Option D. Cpap Or Bipap may also be used for OSA is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 16 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 10</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Oral Cavity & Oropharynx - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 10</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 10 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Most preferred radiological investigation for parotid malignancy is:", "options": [{"label": "A", "text": "Pet-CT", "correct": false}, {"label": "B", "text": "USG", "correct": false}, {"label": "C", "text": "CT", "correct": false}, {"label": "D", "text": "MRI", "correct": true}], "correct_answer": "D. MRI", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>MRI MRI is the most preferred radiological investigation for parotid malignancy.</p>\n<p><strong>Highyeild:</strong></p><p>PAROTID MALIGNANCY MRI of the head and neck should be performed. MRI is superior to CT in our experience and others, although some workers find MRI and CT comparable. For MRI, enhancement with gadolinium should be used to identify and delineate tumors accurately. Both imaging techniques demonstrate brain invasion on MRI and perineural spread. Sometimes, a magnetic resonance angiogram will help delineate extensive vessel involvement and assess patency.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Oral hairy leukoplakia is caused by:", "options": [{"label": "A", "text": "Cytomegalovirus", "correct": false}, {"label": "B", "text": "Epstein Barr Virus", "correct": true}, {"label": "C", "text": "Human Papilloma Virus", "correct": false}, {"label": "D", "text": "Herpes Simplex Virus", "correct": false}], "correct_answer": "B. Epstein Barr Virus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Epstein Barr Virus Epstein Barr Virus causes oral hairy leukoplakia.</p>\n<p><strong>Highyeild:</strong></p><p>ORAL HAIRY LEUKOPLAKIA It is a white, vertically corrugated lesion on the anterior part of the lateral border of the tongue. The Epstein-Barr virus probably causes it. A biopsy should be done to confirm.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 5-year male child was brought by his mother in OPD with a complaint of mouth breathing and snoring while sleeping. All of the following are true regarding this condition except:", "options": [{"label": "A", "text": "Patient presents with c/o snoring with mouth breathing", "correct": false}, {"label": "B", "text": "Definitive contraindication for surgery is cleft palate and palatal paralysis", "correct": false}, {"label": "C", "text": "One of the complications of surgery for the condition is Grisel syndrome", "correct": false}, {"label": "D", "text": "Bleeding within 24 hr after this surgery is called a secondary hemorrhage", "correct": true}], "correct_answer": "D. Bleeding within 24 hr after this surgery is called a secondary hemorrhage", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004739899-QTDE061004IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Bleeding within 24 hr after this surgery is called a secondary hemorrhage The above case history and examination findings describe adenoid hypertrophy. Chronic nasal obstruction and mouth breathing lead to a characteristic facial appearance called adenoid facies. The child has an elongated face with a dull expression, an open mouth, prominent and crowded upper teeth, and a hitched-up upper lip. Secondary hemorrhage is usually seen between the fifth and tenth postoperative day of adenoidectomy.</p>\n<p><strong>Highyeild:</strong></p><p>Adenoid facies. Patient is a mouth breather. Adenoid tissue is seen on MRI in all infants by age of 5 months, gradually it increases in size and is at its maximum on 6-7 years. Starts regressing at puberty and disappears by the age of 15 years. Persistence of tissue may be seen beyond 15 years in cases of allergy or infection. Symptoms of adenoid disease depend on the comparative size of nasopharynx.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Patient presents with c/o snoring with mouth breathing in adenoid hypertrophy is a true statement. Option B., the Definitive contraindication for surgery, is cleft palate, and palatal paralysis is a true statement. Option C. One complication of surgery of condition is Grisel syndrome, in which the Patient complains of neck pain and develops torticollis. Mostly it is due to spasms of paraspinal muscles, but it can be due to atlantoaxial dislocation requiring a cervical collar and even traction.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All Are Methods Of Identification Of Facial Nerve In Parotid Surgery Except:", "options": [{"label": "A", "text": "Nerve lies 1 cm deep and slightly anterior and inferior to cartilaginous pointer", "correct": false}, {"label": "B", "text": "If the post belly of Digastric is traced backward", "correct": false}, {"label": "C", "text": "6-8 mm deep to Tympanomastoid suture", "correct": false}, {"label": "D", "text": "4 cm above the angle of the mandible", "correct": true}], "correct_answer": "D. 4 cm above the angle of the mandible", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>4 cm above the angle of the mandible The facial nerve lying 4 cm above the angle of the mandible is a wrong statement for identifying the facial nerve in parotid surgery.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Nerve lies 1cm deep, slightly anterior and inferior to the pointer is a true statement. O ption B. If the posterior belly of the digastric is traced backward along its upper border to its attachment to the groove, the nerve is found to lie between it and the styloid process. O ption C. Nerve lies 6-8 mm deep to Tympanomastoid suture is a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An unimmunized 2-year-old girl presents with high-grade fever & sore throat. On examination, she is toxic in appearance & having bilateral cervical lymphadenopathy throat examination shows the following findings diagnosis:", "options": [{"label": "A", "text": "Diphtheria", "correct": true}, {"label": "B", "text": "Candidiasis", "correct": false}, {"label": "C", "text": "Acute membranous tonsillitis", "correct": false}, {"label": "D", "text": "Vincent’s angina", "correct": false}], "correct_answer": "A. Diphtheria", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004741126-QTDE061006IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Diphtheria The above image shows a Greyish white membrane on the tonsil, pharynx, and soft palate. Also, from the above history and examination, it is clear that the correct answer is Diptheria.</p>\n<p><strong>Highyeild:</strong></p><p>Features of Diphtheria: General symptoms - Onset is insidious with low-grade fever (100–101°F), sore throat, and malaise, but the patient is very toxemic with tachycardia and a thready pulse. Laryngeal symptoms - Hoarse voice, croupy cough, inspiratory stridor, increasing dyspnea with marked upper airway obstruction Membrane - Greyish white membrane is seen on the tonsil, pharynx, and soft palate. It is adherent and its removal leaves a bleeding surface. A similar membrane is seen over the larynx and trachea. Cervical lymphadenopathy- Characteristic “bull-neck” may be seen.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption B. Candidiasis appears as white-grey patches on the oral mucosa and tongue. When wiped off, they leave an erythematous mucosa. The condition is seen in infants and children. Adults are also affected when they are suffering from systemic malignancy and diabetes or taking broad-spectrum antibiotics, cytotoxic drugs, steroids, or radiation. O ption C. Acute membranous tonsillitis occurs due to pyogenic organisms. An exudative membrane forms over the medial surface of the tonsils, along with the features of acute tonsillitis. O ption D. Vincent's angina is insidious in onset, with less fever and less discomfort in the throat. The membrane, which usually forms over one tonsil, can be easily removed, revealing an irregular ulcer on the tonsil. A throat swab will show both the organisms typical of the disease, namely fusiform bacilli and spirochaetes.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Identify the instrument shown below.", "options": [{"label": "A", "text": "Peritonsillar Abscess Forceps", "correct": false}, {"label": "B", "text": "Lempert’s Curette", "correct": false}, {"label": "C", "text": "Tilley’S Harpoon", "correct": false}, {"label": "D", "text": "St Clair Thomson’s curette", "correct": true}], "correct_answer": "D. St Clair Thomson’s curette", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004743865-QTDE061007IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>St Clair Thomson’s curette The above image shows Clair Thompson’s adenoid curette with a guard.</p>\n<p><strong>Highyeild:</strong></p><p>St. Clair Thompson’s adenoid curette They are used in Curette shaves off the adenoid mass while the guard holds the tissue, preventing it from slipping. With the advent and use of debrided and coblation techniques, the curette is declining.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Peritonsillar Abscess Forceps O ption B. Lempert’s Curette Option C. Tilley’S Harpoon</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An immune-compromised patient presented with a burning sensation over the tongue. Diagnosis?", "options": [{"label": "A", "text": "Candidiasis", "correct": false}, {"label": "B", "text": "Herpes zoster oticus", "correct": false}, {"label": "C", "text": "Oral hairy leukoplakia", "correct": true}, {"label": "D", "text": "Submucous Fibrosis", "correct": false}], "correct_answer": "C. Oral hairy leukoplakia", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004744632-QTDE061009IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Oral hairy leukoplakia It is a white, vertically corrugated lesion on the anterior part of the lateral border of the tongue . It is probably caused by Epstein–Barr virus . A biopsy should be done to confirm.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Candidiasis appears as white-grey patches on the oral mucosa and tongue. When wiped off, they leave an erythematous mucosa. The condition is seen in infants and children. Adults are also affected when they are suffering from systemic malignancy and diabetes or taking broad-spectrum antibiotics, cytotoxic drugs, steroids, or radiation. O ption B. Herpes zoster oticus is characterized by the formation of cysts on the tympanic membrane, metal skin, concha, and postauricular groove. The VIIth and VIIIth cranial nerves may be involved. O ption D Submucous Fibrosis is characterized by intolerance to chilies and spicy food, Soreness of mouth with constant burning sensation, worsened during meals, particularly of savory, spicy type: difficulty opening the mouth thoroughly, Difficulty protruding the tongue.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following structures is seen in the oropharynx?", "options": [{"label": "A", "text": "Rathke pouch", "correct": false}, {"label": "B", "text": "Post cricoid region", "correct": false}, {"label": "C", "text": "Palatine Tonsil", "correct": true}, {"label": "D", "text": "Pyriform Sinus", "correct": false}], "correct_answer": "C. Palatine Tonsil", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Palatine Tonsil Palatine tonsil is seen in the oropharynx</p>\n<p><strong>Highyeild:</strong></p><p>WALDEYER RING</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Rathke pouch is a part of the nasopharynx. O ption B. Post cricoid region is a part of the hypopharynx. O ption D. Pyriform Sinus is also a part of the hypopharynx.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Ipsilateral immobility of soft palate is seen in?", "options": [{"label": "A", "text": "Lermoyez Syndrome", "correct": false}, {"label": "B", "text": "Ortner’s Syndrome", "correct": false}, {"label": "C", "text": "Costen’s Syndrome", "correct": false}, {"label": "D", "text": "Trotter’s Syndrome", "correct": true}], "correct_answer": "D. Trotter’s Syndrome", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Trotter’s Syndrome Trotter's syndrome is a clinical triad of unilateral deafness, neuralgia affecting branches of the trigeminal nerve, and impaired mobility of the soft palate caused by malignant tumors involving the lateral pharyngeal recess (Rosenmüller's fossa).</p>\n<p><strong>Highyeild:</strong></p><p>Trotter (or Sinus of Morgagni) syndrome or triad It is seen in nasopharyngeal carcinoma, which spreads laterally to involve the sinus of Morgagni involving the mandibular nerve. It is characterized by: Conductive hearing loss (due to eustachian tube obstruction). Ipsilateral immobility of soft palate. Neuralgic pain in the distribution of V3.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. In Lermoyez syndrome, symptoms of Ménière’s disease are seen in reverse order. First, there is a progressive deterioration of hearing, followed by an attack of vertigo, at which time the hearing recovers. O ption B. Ortner’s Syndrome is paralysis of recurrent laryngeal nerve and cardiomegaly. O ption C. Costen syndrome is an abnormality of a temporomandibular joint due to a defective bite. It is characterized by otalgia, the feeling of a blocked ear, tinnitus, and sometimes vertigo. Pain also radiates to frontal, parietal and occipital region.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Identify the pathology depicted in the Image. Is it the oral cavity image from a chronic tobacco chewer patient?", "options": [{"label": "A", "text": "Oral Submucous Fibrosis", "correct": true}, {"label": "B", "text": "Leukoplakia", "correct": false}, {"label": "C", "text": "Erythroplakia", "correct": false}, {"label": "D", "text": "Squamous Cell Carcinoma", "correct": false}], "correct_answer": "A. Oral Submucous Fibrosis", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004747036-QTDE061013IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Oral Submucous Fibrosis Note the blanched appearance of the soft palate and faucial pillars in the given Image, the question also says the patient is a chronic tobacco chewer, which says the diagnosis is Submucosal fibrosis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption B. Leukoplakia is a clinical white patch that cannot be characterized clinically or pathologically as any other disease. It is a clinical definition and does not consider pathology. O ption C. erythroplakia is a red patch or plaque on the mucosal surface. The red color is due to decreased keratinization, and as a result, the red vascular connective tissue of the submucosa shines through O ption D. SCC cannot be diagnosed just by seeing this Image. Histology is necessary.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 20 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 17</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Otosclerosis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 17</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 17 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "All Are True About Otosclerosis Except:", "options": [{"label": "A", "text": "Pt hears better in a noisy environment", "correct": false}, {"label": "B", "text": "Only one hearing ear is C/I for surgery", "correct": false}, {"label": "C", "text": "Cahart`s notch is due to the loss of the inertial component of bone conduction.", "correct": false}, {"label": "D", "text": "The most typical size is a round window.", "correct": true}], "correct_answer": "D. The most typical size is a round window.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>The most typical size is a round window. Most common site involved is fistula ante fenestrae .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- In otosclerosis, Paracusis Willis is seen; the patient listens better in noisy surroundings. Option: B.- only if one ear is affected; it is a contraindication for surgery. Option: C- Cahart`s notch is due to the loss of the inertial component of bone conduction. Carhart crack is present in stapedial otosclerosis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Most common site for the initiation of otosclerosis is", "options": [{"label": "A", "text": "The foot plate of stapes", "correct": false}, {"label": "B", "text": "Margins of Stapes", "correct": false}, {"label": "C", "text": "Fissula Antefenestrum", "correct": true}, {"label": "D", "text": "Fissula Post Fenestrum", "correct": false}], "correct_answer": "C. Fissula Antefenestrum", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Fissula Antefenestrum The most common site of otosclerosis is Fissula ante fenestrae (i.e., just in front of the oval window).</p>\n<p><strong>Extraedge:</strong></p><p>The most common type of otosclerosis – Stapedial otosclerosis Most common site of otosclerosis – Fissula ante fenestrae (i.e., just in front of the oval window) Most common site for stapedial otosclerosis – Fissula ante fenestrae (i.e., just in front of the oval window) The most common site for cochlear otosclerosis – Round window</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In otosclerosis, tinnitus is due to:", "options": [{"label": "A", "text": "Cochlear Otosclerosis", "correct": true}, {"label": "B", "text": "Increase vascularity in lesions", "correct": false}, {"label": "C", "text": "Conductive Deafness", "correct": false}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "A. Cochlear Otosclerosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cochlear Otosclerosis Tinnitus is more in cochlear otosclerosis, indicating sensorineural degeneration. Points to Remember-- Paracusis Patient hears better in noise. They are seen in otosclerosis. Presbycusis SNHL is associated with aging. Manifests at 65 years of age. (It is physiological) Hyperacusis is a Sensation of discomfort/pain on exposure to loud noises. We have seen an injury to the nerve to the stapedius. Diplacusis Patient hears the same tone as of different pitches in either ear (distortion of sound). Seen in Meniere’s disease</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Characteristic features of otosclerosis are all except", "options": [{"label": "A", "text": "Conductive Deafness", "correct": false}, {"label": "B", "text": "Positive Rinne's Test", "correct": true}, {"label": "C", "text": "Paracusis Willisii", "correct": false}, {"label": "D", "text": "Mobile Ear Drum", "correct": false}], "correct_answer": "B. Positive Rinne's Test", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Positive Rinne's Test Tuning fork tests show negative Rinne (i.e., BC > AC) first for 256 Hz, then 512 Hz, and later, when stapes fixation is complete, for 1026 Hz.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- As a stapes footplate is involved, these patients will have conductive hearing loss. Option: C- Paracusis Willisii is seen; the patient hears better in a noisy environment. Option: D- Tympanic membrane is normal and mobile in 90% of cases.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A pure tone audiogram with a dip at 2000 Hz is characteristic of:", "options": [{"label": "A", "text": "Presbycusis", "correct": false}, {"label": "B", "text": "Ototoxicity", "correct": false}, {"label": "C", "text": "Otosclerosis", "correct": true}, {"label": "D", "text": "Nose-induced hearing loss", "correct": false}], "correct_answer": "C. Otosclerosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Otosclerosis A pure tone audiogram with a dip at 2000 Hz is characteristic of otosclerosis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is the most common type of otosclerosis?", "options": [{"label": "A", "text": "Stapedial Otosclerosis", "correct": true}, {"label": "B", "text": "Cochlear Otosclerosis", "correct": false}, {"label": "C", "text": "Histological Otosclerosis", "correct": false}, {"label": "D", "text": "Both A and C", "correct": false}], "correct_answer": "A. Stapedial Otosclerosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Stapedial Otosclerosis The most common type of otosclerosis is stapedial otosclerosis.</p>\n<p><strong>Highyeild:</strong></p><p>Stapedial otosclerosis causing stapes fixation and conductive deafness is the most common variety. The lesion starts just in front of the oval window in an area called “ fistula ante fenestrae .” This is the site of preference (anterior focus). The lesion may start behind the oval window (posterior fixation), around the margin of the stapes footplate (circumferential), in the footplate, but the annual ligament is free (biscuit type). Sometimes, it may completely obliterate the oval window niche (obliterative kind).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: B- COCHLEAR OTOSCLEROSIS involves the region of a round window, The most common type of otosclerosis,s,nd may cause sensorineural hearing loss, probably due to the liberation of toxic materials into the inner ear fluid. Option: C - HISTOLOGIC OTOSCLEROSIS remains asymptomatic and causes neither conductive nor sensorineural hearing loss.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 34-Year-old female presented with a complaint of tinnitus and bilateral progressive hearing loss; on examination of Pure Tone Audiometry, AB bone conduction dip is seen at 2000Hz. All of the following statements are true except?", "options": [{"label": "A", "text": "Gelles Negative", "correct": false}, {"label": "B", "text": "Carhart’s notch disappears after a successful stapedectomy", "correct": false}, {"label": "C", "text": "Pure tone audiometry shows loss of air conduction, more for lower frequencies", "correct": false}, {"label": "D", "text": "Otosclerosis of left ear, note dip at 2000 Hz in AC", "correct": true}], "correct_answer": "D. Otosclerosis of left ear, note dip at 2000 Hz in AC", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004676464-QTDE062010IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Otosclerosis of left ear, note dip at 2000 Hz in AC Dip is seen in BC ; not in AC.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- Gelle’s test is negative for otosclerosis. Option: B- Carhart’s notch disappears after successful stapedectomy. Option: C- Pure tone audiometry shows loss of air conduction, more for lower frequencies.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 2-year-old female patient comes to you in the ENT department. She presented with progressive hearing loss. Rinne’s test is negative, and the sound lateralized to the worst ear on Weber's test. What is your diagnosis?", "options": [{"label": "A", "text": "Otosclerosis", "correct": true}, {"label": "B", "text": "Meniere’s Disease", "correct": false}, {"label": "C", "text": "Acoustic Neuroma", "correct": false}, {"label": "D", "text": "Sudden sensorineural hearing loss", "correct": false}], "correct_answer": "A. Otosclerosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Otosclerosis The above clinical scenario is suggestive of otosclerosis. Otosclerosis is autosomal dominant and more common in females . It usually presents with bilateral progressive conductive hearing loss. Otosclerosis is known to be initiated or worsened by stress & hormonal variations like during pregnancy, trauma, surgery, and menopause.</p>\n<p><strong>Highyeild:</strong></p><p>Otoscopic findings in otosclerosis . The tympanic membrane is normal and mobile in 90% of cases. Schwartz signs—Flamingo pink in the tympanic membrane is seen in 10% of cases. It indicates active Focus with increased vascularity. Stapes footplate—Shows a rice grain/biscuit-type appearance Blue mantles are seen histopathologically.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: B - Meniere's disease is more common in males and is unilateral. Option: C - Acoustic neuroma has no sex predilection and usually presents after 40 years with unilateral sensorineural hearing loss. Option: D - In sudden sensorineural hearing loss Rinne's test is positive, and making Weber sound will lateralize to the opposite ear.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "For research on pharmacological treatment in otosclerosis, a researcher wanted to set the age limit of the study to the most common age group when hearing loss usually manifests. Which of the following will be suitable?", "options": [{"label": "A", "text": "5-15 Years", "correct": false}, {"label": "B", "text": "10-20 Years", "correct": false}, {"label": "C", "text": "20-30 Years", "correct": true}, {"label": "D", "text": "30-40 Years", "correct": false}], "correct_answer": "C. 20-30 Years", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>20-30 Years The most common age group when hearing loss in otosclerosis usually manifests starts between 20 and 30 years of age. It is rare before ten and after 40 years.</p>\n<p><strong>Highyeild:</strong></p><p>Otoscopic findings in otosclerosis . The tympanic membrane is normal and mobile in 90% of cases. Schwartz signs—Flamingo pink in the tympanic membrane is seen in 10% of cases. It indicates active Focus with increased vascularity. Stapes footplate—Shows a rice grain/biscuit-type appearance Blue mantles are seen histopathologically.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are an ENT senior resident posted in a remote village who comes across a patient with whom you suspect otosclerosis. Which of the following tests can confirm the diagnosis clinically?", "options": [{"label": "A", "text": "Gelle's Test", "correct": true}, {"label": "B", "text": "Stenger's Test", "correct": false}, {"label": "C", "text": "Schwabach Test", "correct": false}, {"label": "D", "text": "Rinne's Test", "correct": false}], "correct_answer": "A. Gelle's Test", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Gelle's Test Gelle's is a tuning fork test done to clinically confirm otosclerosis in the olden days . It is based on the fact that in a normal ear, if pressure is increased in the external ear, the stapes footplate will get medialized and stop conducting sound . But, in an ear with otosclerosis of the footplate, there will be no movements, and the patient will continue to hear the sound.</p>\n<p><strong>Highyeild:</strong></p><p>Otoscopic findings in otosclerosis . The tympanic membrane is normal and mobile in 90% of cases. Schwartz signs—Flamingo pink in the tympanic membrane is seen in 10% of cases. It indicates active Focus with increased vascularity. Stapes footplate—Shows a rice grain/biscuit-type appearance Blue mantles are seen histopathologically.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: B - Stenger's test is done to rule out malingering. Option: C - Schwabach is a tuning fork test that compares the bone conduction of the patient with that of the examiner. Option: D - Rinne's is a tuning fork test comparing bone and air conduction.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 29-year-old Caucasian woman has a history of progressive hearing impairment, which improves in a noisy environment. Tuning fork tests revealed a conductive hearing loss in both ears. What is the most probable diagnosis among the given options?", "options": [{"label": "A", "text": "Otosclerosis", "correct": true}, {"label": "B", "text": "CSOM", "correct": false}, {"label": "C", "text": "Meniere’s Disease", "correct": false}, {"label": "D", "text": "Otitis media with effusion", "correct": false}], "correct_answer": "A. Otosclerosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Otosclerosis The given case scenario is a case of otosclerosis . There is bilateral conductive deafness , paracusis Willis and a normal tympanic membrane which suggest a diagnosis of otosclerosis.</p>\n<p><strong>Highyeild:</strong></p><p>Otoscopic findings in otosclerosis . The tympanic membrane is normal and mobile in 90% of cases. Schwartz signs—Flamingo pink in the tympanic membrane is seen in 10% of cases. It indicates active Focus with increased vascularity. Stapes footplate—Shows a rice grain/biscuit-type appearance Blue mantles are seen histopathologically.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: B - A case of CSOM will have a perforated tympanic membrane. Option: C - Meniere's disease has a sensorineural type of hearing loss. Option: D - In the case of otitis media with effusion, the tympanic membrane is often dull and opaque with a loss of light reflex.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 26 years old pregnant woman has bilateral hearing loss. She was diagnosed to have otosclerosis. You are willing to perform a stapedotomy on this patient with otosclerosis. After appropriate dissection, where will you place the piston?", "options": [{"label": "A", "text": "Between incus and stapes head", "correct": false}, {"label": "B", "text": "Between incus and oval window", "correct": true}, {"label": "C", "text": "Between malleus and incus", "correct": false}, {"label": "D", "text": "Between malleus and oval window", "correct": false}], "correct_answer": "B. Between incus and oval window", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Between incus and oval window The piston is placed between the incus and the oval window . Stapedotomy is done to bypass the immobile stapes using a piston that will take over conducting sound . For this reason, the piston replaces the removed stapes superstructure.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 26 years old pregnant woman has bilateral hearing loss. She was diagnosed to have otosclerosis. You are willing to perform a stapedotomy on this patient with otosclerosis. This patient with otosclerosis underwent stapedotomy; which of the following complications is least likely?", "options": [{"label": "A", "text": "Dead Ear", "correct": false}, {"label": "B", "text": "Sensorineural Hearing Loss", "correct": false}, {"label": "C", "text": "Cholesteatoma", "correct": true}, {"label": "D", "text": "Facial Palsy", "correct": false}], "correct_answer": "C. Cholesteatoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cholesteatoma Cholesteatoma is not a complication of stapedotomy.</p>\n<p><strong>Highyeild:</strong></p><p>Complications of stapedectomy Sensorineural hearing loss Injury to the facial nerve Floating/ submerged footplate Endolymph gush due to damage to the inner ear Dislodgement of piston Post-op vertigo Dead ear</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- dead ear is a complication of stapedectomy. Option: B- Sensorineural Hearing Loss is a complication due to intraoperative trauma/ labyrinthitis. Option: D- Injury to the facial nerve can occur.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are an ENT surgeon, and the following are a few patients with otosclerosis. For which of the following patients will you advise stapedotomy? 1. A factory worker with no other comorbidities 2. A 30-year-old male with Meniere's disease 3. A 25-year-old male commercial pilot 4. A 40-year-old female with Bronchial asthma 5. A middle-aged man with diabetes Select the correct answer from the code below:", "options": [{"label": "A", "text": "1,3,4 Only", "correct": false}, {"label": "B", "text": "1 and 4 only", "correct": false}, {"label": "C", "text": "2 & 5 only", "correct": false}, {"label": "D", "text": "4 & 5 only", "correct": true}], "correct_answer": "D. 4 & 5 only", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>4 & 5 only Contraindications for Stapedotomy: Only hearing ear Positive Schwartze sign Otitis externa Meniere's disease: can damage the inner ear while drilling a hole in the footplate. Young children, Pregnant ladies, Professional athletes, pilots, construction workers, and divers: Sudden changes in pressure can displace the piston and cause vertigo. Those who work in noisy surroundings: Surgery makes them more vulnerable to noise-induced hearing loss.</p>\n<p><strong>Highyeild:</strong></p><p>Contraindications for Stapedotomy: Only hearing ear Positive Schwartze sign Otitis externa Meniere's disease: can damage the inner ear while drilling a hole in the footplate. Young children, Pregnant ladies, Professional athletes, pilots, construction workers, and divers: Sudden changes in pressure can displace the piston and cause vertigo. Those who work in noisy surroundings: Surgery makes them more vulnerable to noise-induced hearing loss.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 29-year-old Caucasian woman came with a history of progressive hearing impairment which improves in a noisy environment. Tuning fork tests revealed a conductive hearing loss in both ears. She was diagnosed to have otosclerosis. All are true about otosclerosis except", "options": [{"label": "A", "text": "Increased incidence in females", "correct": false}, {"label": "B", "text": "Conductive Deafness", "correct": false}, {"label": "C", "text": "Irreversible loss of hearing", "correct": true}, {"label": "D", "text": "Carhartz notch at 2000 Hz", "correct": false}], "correct_answer": "C. Irreversible loss of hearing", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Irreversible loss of hearing Since the hearing loss in otosclerosis is due to fixation of the footplate of stapes so the mobilization of stapes by stapedotomy reverses the hearing loss. So hearing loss is reversible by stapedotomy.</p>\n<p><strong>Highyeild:</strong></p><p>Otoscopic findings in otosclerosis . Tympanic membrane is normal and mobile in 90% of cases. Schwartz sign—Flamingo pink of the tympanic membrane is seen in 10% of cases. It indicates active Focus with increased vascularity. Stapes footplate—Shows a rice grain/biscuit-type appearance Blue mantles are seen histopathologically.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- Females have more incidence of otosclerosis than males. Option: B- Largely there, conductive deafness occurs, but when there is the involvement of cochlea, i.e., cochlear otosclerosis, sensory neural deafness is also seen. Option: D- Carhartz notch at 2000 Hz is seen in the bone conduction curve due to the absence of additional vibrations at this frequency. Not seen in the air conduction curve.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An elderly woman with a history of otosclerosis has now come with worsening deafness. She attained menopause one year ago. What would be the most likely report if impedance audiometry was done for her?", "options": [{"label": "A", "text": "As Graph Alone", "correct": false}, {"label": "B", "text": "Ad graph with increased stapedial reflex", "correct": false}, {"label": "C", "text": "As graph with loss of stapedial reflex", "correct": true}, {"label": "D", "text": "As graph with increased stapedial reflex", "correct": false}], "correct_answer": "C. As graph with loss of stapedial reflex", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>As graph with loss of stapedial reflex The above case suggests a long-standing case of otosclerosis, and tympanometry will show As graph and loss of stapedial reflex .</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 24 years old primipara is being evaluated for hearing impairment which had worsened during her pregnancy. Rinne's test is negative in both the ears, and Weber's lateralized to the right side. On otoscopy, tympanic membranes appear normal. Audiometry shows a dip in BC at 2000 Hz. What is the treatment of choice for the above condition?", "options": [{"label": "A", "text": "Sodium Fluoride", "correct": false}, {"label": "B", "text": "Tympanoplasty", "correct": false}, {"label": "C", "text": "Stapedectomy", "correct": false}, {"label": "D", "text": "Stapedotomy", "correct": true}], "correct_answer": "D. Stapedotomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Stapedotomy The above clinical scenario suggests otosclerosis and the treatment of choice for otosclerosis is stapedotomy Stapedotomy is the excision of the superstructure of stapes and, drilling a hole in the footplate & placing a piston in the hole. This is the treatment of choice.</p>\n<p><strong>Highyeild:</strong></p><p>Otoscopic findings in otosclerosis . Tympanic membrane is normal and mobile in 90% of cases. Schwartz sign—Flamingo pink of the tympanic membrane is seen in 10% of cases. It indicates active Focus with increased vascularity. Stapes footplate—Shows a rice grain/biscuit-type appearance Blue mantles are seen histopathologically.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- Sodium fluoride is used in an active case of otosclerosis to hasten maturation. Option: B- tympanoplasty is a tympanic membrane reconstruction and is not done in otosclerosis. Option: C- Stapedectomy is the excision of the entire stapes (superstructure + footplate) and placing a piston in its place. It is outdated.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 27 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 10</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Pharyngeal Tumours - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 10</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 10 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "An ENT postgraduate resident is doing her thesis on nasopharyngeal carcinomas and asks you, the intern on ENT rotation, about NPC's viral etiology. Which of the following viruses is one of the etiologies of nasopharyngeal carcinomas?", "options": [{"label": "A", "text": "EBV", "correct": true}, {"label": "B", "text": "HPV", "correct": false}, {"label": "C", "text": "HIV", "correct": false}, {"label": "D", "text": "HHV", "correct": false}], "correct_answer": "A. EBV", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>EBV Epstein–Barr (EBV) virus is closely associated with nasopharyngeal cancer.</p>\n<p><strong>Highyeild:</strong></p><p>The exact aetiology of NPC is not known. The factors responsible are: Chinese have a higher genetic susceptibility to nasopharyngeal cancer. Even after migration to other countries, they continue to have a higher incidence. Epstein–Barr (EBV) virus is closely associated with nasopharyngeal cancer. Specific viral markers are being developed to screen people in high-incidence areas. EB virus has two important antigens: viral capsid antigen (VCA) and early antigen (EA). IgA antibodies of EA are particular for nasopharyngeal cancer but have a sensitivity of only 70–80%, while IgA antibodies of VCA are more sensitive but less specific. AgA antibodies against both EA and VCA should be done for screening patients for nasopharyngeal cancer. Environmental. Air pollution, smoking of tobacco and opium, nitrosamines from dry salted fish, and smoke from the burning of incense and wood have all been incriminated.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The investigation of choice for the diagnosis of the suspected condition in a 15-year-old boy who has presented to your ENT OPD with complaints of recurrent, profuse and spontaneous epistaxis is:", "options": [{"label": "A", "text": "Biopsy", "correct": false}, {"label": "B", "text": "CECT", "correct": true}, {"label": "C", "text": "MRI", "correct": false}, {"label": "D", "text": "Carotid Angiography", "correct": false}], "correct_answer": "B. CECT", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>CECT A juvenile male with severe and recurrent epistaxis suggests juvenile nasopharyngeal angiofibroma. Computed tomography (CT) scan of the head with contrast enhancement (CECT) is now the investigation of choice. It has replaced conventional radiographs. It shows the extent of the tumour, bony destruction or displacements. Anterior bowing of the maxillary sinus's posterior wall, often called an antral sign or Holman-Miller sign, is pathognomic of angiofibroma.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Biopsy of the tumour is attended with profuse bleeding and is, therefore, avoided. If it is essential to differentiate it from other tumours, a biopsy can be done under general anaesthesia with all arrangements to control bleeding and transfuse blood. Option C. Magnetic resonance imaging (MRI) complements CT scans and shows any soft tissue extensions present intracranially in the infratemporal fossa or orbit. Option D. Carotid angiography shows the extent of tumours, their vascularity and feeding vessels mainly from the external carotid system. In very large tumours or those with intracranial extension, vessels may also come from the internal carotid system. Embolisation of vessels can be done at this time to decrease bleeding during the Feeders from only the external carotid system can be embolised. Resection of the tumour should not be delayed beyond 24–48 h of embolisation to avoid revascularisation from the contralateral side.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "CECT of a teenage boy with recurrent, profuse and spontaneous epistaxis is shown below. The pathognomic sign of the associated condition is:", "options": [{"label": "A", "text": "Tear Drop Sign", "correct": false}, {"label": "B", "text": "Holman Miller Sign", "correct": true}, {"label": "C", "text": "Double-Density Sign", "correct": false}, {"label": "D", "text": "Reservoir Sign", "correct": false}], "correct_answer": "B. Holman Miller Sign", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003927807-QTDE077004IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Holman Miller Sign Anterior bowing of the maxillary sinus's posterior wall often called an antral sign or Holman-Miller sign, is pathognomic of juvenile nasopharyngeal angiofibroma.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. The teardrop sign refers to the appearance of herniated intraorbital fat (+/- inferior rectus muscle), which has protruded through a fracture of the inferior orbital wall. This typically occurs following a \"blow-out\" fracture during a punch to the orbit. O ption C. Allergic fungal sinusitis on CT, shows a \"double density\" sign: thick fungal mucin surrounded by hyperplasia. O ption D. Reservoir, sign or teapot sign is seen in CSF rhinorrhea.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient in your ENT OPD suffering from nasopharyngeal carcinoma shows the Trotters triad clinical features. This does not comprise of:", "options": [{"label": "A", "text": "Ipsilateral Temporoparietal Neuralgia", "correct": false}, {"label": "B", "text": "Conductive Deafness", "correct": false}, {"label": "C", "text": "Epistaxis", "correct": true}, {"label": "D", "text": "Palatal Paralysis", "correct": false}], "correct_answer": "C. Epistaxis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Epistaxis Trotter’s triad does not include epistaxis</p>\n<p><strong>Highyeild:</strong></p><p>TROTTER’S TRIAD It is seen in nasopharyngeal cancer. It includes Conductive deafness (eustachian tube blockage) Ipsilateral temporoparietal neuralgia (involvement of CN V- mandibular branch) Palatal paralysis (CN X)</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Treatment of very aggressive recurrent tumours and residual lesions of juvenile nasopharyngeal angiofibroma in a patient you’ve already operated upon previously is:", "options": [{"label": "A", "text": "Wait And Watch", "correct": false}, {"label": "B", "text": "Chemotherapy", "correct": true}, {"label": "C", "text": "Radiotherapy", "correct": false}, {"label": "D", "text": "Hormonal Therapy", "correct": false}], "correct_answer": "B. Chemotherapy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Chemotherapy Very aggressive recurrent tumours and residual lesions have been treated by chemotherapy. Doxorubicin, vincristine and dacarbazine have been used in combination. Chemotherapy and radiotherapy can arrest the growth and cause some tumour regression but not total tumour eradication.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Spontaneous regression of the tumour with the advancement of age, as thought previously, does not occur, and no wait-and-watch policy should be adopted. O ption C. Treatment with radiotherapy is controversial. Some believe all large tumours with intracranial extension should be treated with radiation, while others reserve it for recurrent inoperable tumours. Radiation to the nasopharynx in the young has the risk of developing malignancy at a later age. O ption D. Hormonal therapy. Since the tumour occurs in young males at puberty, probably activated by testosterone, hormonal therapy has been used as the primary or adjunctive treatment. Diethylstilbestrol and flutamide (an androgen blocker) have been used to arrest the growth, but no significant regression has been observed in practice.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "ENT clinic is 3rd year and is in progress, and the topic of discussion is tumours of the nasopharynx. All of the following are benign tumours, except:", "options": [{"label": "A", "text": "Choanal Polyp", "correct": false}, {"label": "B", "text": "Pleomorphic Adenoma", "correct": false}, {"label": "C", "text": "Nasopharyngeal Angiofibroma", "correct": false}, {"label": "D", "text": "Plasmacytoma", "correct": true}], "correct_answer": "D. Plasmacytoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Plasmacytoma Plasmacytoma is a malignant tumour of the nasopharynx, not benign.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption Choanal Polyp is a benign tumour of the nasopharynx. O ption Pleomorphic Adenoma is also a benign tumour of the nasopharynx. Option C. Nasopharyngeal Angiofibroma is also a benign tumour of the nasopharynx</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 60-year-old Chinese man with nasopharyngeal carcinoma comes to your clinic. Distant metastases in this patient are not seen in:", "options": [{"label": "A", "text": "Lungs", "correct": false}, {"label": "B", "text": "Kidney", "correct": true}, {"label": "C", "text": "Liver", "correct": false}, {"label": "D", "text": "Bones", "correct": false}], "correct_answer": "B. Kidney", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Kidney Lung, bone and liver are the most common sites in distant metastasis in nasopharyngeal carcinoma.</p>\n<p><strong>Highyeild:</strong></p><p>ROUTE OF SPREAD AND CLINICAL FEATURES IN NASOPHARYNGEAL CARCINOMA</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient with nasopharyngeal carcinoma in your hospital has a tumour extending to the oropharynx's soft tissues. According to the latest TNM classification, it is classified as follows:", "options": [{"label": "A", "text": "T1", "correct": false}, {"label": "B", "text": "T2", "correct": true}, {"label": "C", "text": "T3", "correct": false}, {"label": "D", "text": "T4", "correct": false}], "correct_answer": "B. T2", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>T2 A tumour extending to the oropharynx's soft tissues is classified under the T2 stage of nasopharyngeal carcinoma.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. T1 is a tumour confined to the nasopharynx. O ption C. T3 is when the tumour invades bony structures/paranasal sinuses. Option D. T4 is a tumour with intracranial extension.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "During your ENT internship, you encountered a case of NPC in the ward. The most common cranial nerve palsy in nasopharyngeal carcinoma is:", "options": [{"label": "A", "text": "CN 4", "correct": false}, {"label": "B", "text": "CN 5", "correct": true}, {"label": "C", "text": "CN 6", "correct": false}, {"label": "D", "text": "CN 7", "correct": false}], "correct_answer": "B. CN 5", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>CN 5 CN V paralysis is the most common cranial nerve palsy in nasopharyngeal carcinoma.</p>\n<p><strong>Highyeild:</strong></p><p>Clinical features in nasopharyngeal cancer</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A geriatric Chinese man presents to the ENT OPD with unilateral serous otitis media and complaints of nasal obstruction. High-resolution CECT of the neck and nasopharynx reveals a growth originating in the fossa of Rosen muller. The diagnosis is:", "options": [{"label": "A", "text": "Rhabdomyosarcoma", "correct": false}, {"label": "B", "text": "Lymphoma", "correct": false}, {"label": "C", "text": "Nasopharyngeal Carcinoma", "correct": true}, {"label": "D", "text": "Nasopharyngeal Angiofibroma", "correct": false}], "correct_answer": "C. Nasopharyngeal Carcinoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Nasopharyngeal Carcinoma The commonest site of origin in the nasopharynx is the fossa of Rosenmüller. Due to obstruction of the eustachian tube , there is conductive hearing loss and serous or suppurative otitis media. Tinnitus and dizziness may occur. Unilateral serous otitis media in an adult should raise suspicion of nasopharyngeal growth.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Rhabdomyosarcoma is commonly seen in children. Embryonal rhabdomyosarcoma presents as a polypoid mass in the nasopharynx. O ption B. Lymphomas. Non-Hodgkin’s type is more common than Hodgkin’s type. Almost all are B-cell types. T-cell lymphomas are seen in the Asian population. O ption D. Nasopharyngeal angiofibroma presents with otitis media with effusion and is commonly seen in adolescent males.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 20 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 4</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Physiology of Nose & Sinuses - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 4</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 4 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 22-year-old patient presents to you with bronchiectasis and chronic sinusitis. Chest X-Ray shows situs inversus. Which of the following is the method of protection of the lower airway that's compromised in this patient?", "options": [{"label": "A", "text": "Temperature Regulation", "correct": false}, {"label": "B", "text": "Humidification", "correct": false}, {"label": "C", "text": "Mucociliary Mechanism", "correct": true}, {"label": "D", "text": "Mucosal defense with immunoglobulins", "correct": false}], "correct_answer": "C. Mucociliary Mechanism", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mucociliary Mechanism The given clinical scenario with situs inversus, bronchiectasis , and chronic sinusitis is suggestive of Kartagener syndrome. The m ucociliary mechanism is compromised in this case, due to immotile cilia.</p>\n<p><strong>Highyeild:</strong></p><p>Mucociliary mechanism A mucous blanket consists of a superficial mucus layer and a deeper serous layer, floating on the top of cilia, constantly beating to carry it like a “conveyer belt” towards the nasopharynx. It moves at a speed of 5–10 mm/min and the complete sheet of mucus is cleared into the pharynx every 10–20 min. The inspired bacteria, viruses, and dust particles are entrapped on the viscous mucous blanket and then carried to the nasopharynx to be swallowed. cilia beat 10–20 times per second at room temperature. They have a rapid “effective stroke” and a slow “recovery stroke.” In the former, the extended cilia reach the mucus layer. At the same time, in the recovery stroke, they bend and travel slowly in the reverse direction in the thin serous layer, thus moving the mucous blanket in only one direction.</p>\n<p><strong>Extraedge:</strong></p><p>In immotile cilia syndrome, cilia are defective and cannot beat effectively, leading to stagnation of mucus in the nose and sinuses and bronchi causing chronic rhinosinusitis and bronchiectasis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A female patient was found to be positive for SARS-CoV-2 and had a loss of taste and smell. After regaining her ability to smell after 2 months, she reported to the OPD with the complaint that all the smells she perceived were distorted, similar to the smell of burnt rubber. What is this called?", "options": [{"label": "A", "text": "Anosmia", "correct": false}, {"label": "B", "text": "Phantosmia", "correct": false}, {"label": "C", "text": "Parosmia", "correct": true}, {"label": "D", "text": "Olfactory Agnosia", "correct": false}], "correct_answer": "C. Parosmia", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Parosmia Parosmia is the perversion of smell . It is seen classically in the recovery phase of postinfluenzal anosmia and has been recently reported as a rare complication in post-COVID patients. It may be due to misdirected regeneration of nerve fibres.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Anosmia is a total loss of sense of smell. It can result from nasal obstruction due to nasal polypi, enlarged turbinates, or oedema of mucous membrane as in common cold, allergic or vasomotor rhinitis. Option: B. Phantosmia is an olfactory hallucination which makes you detect smells that aren't really there in your environment. Option: D. Olfactory agnosia is an inability to recognize odour sensations despite olfactory processing, language, and intellectual function intact.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A young man was referred to the maxillofacial surgeon with complaints of pain and tenderness over the left cheek, inability to move the left eye, and numbness over the left forehead. He gave a history of chronic sinusitis for two years. Superior orbital fissure syndrome was suspected. Which of the following nerves is least likely to be involved in this condition?", "options": [{"label": "A", "text": "Oculomotor Nerve", "correct": false}, {"label": "B", "text": "Ophthalmic division of trigeminal nerve", "correct": false}, {"label": "C", "text": "Maxillary division of trigeminal nerve", "correct": true}, {"label": "D", "text": "Trochlear Nerve", "correct": false}], "correct_answer": "C. Maxillary division of trigeminal nerve", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Maxillary division of trigeminal nerve Superior orbital fissure syndrome does not involve maxillary division of the trigeminal nerve . The maxillary division of the trigeminal nerve does not pass through the superior orbital fissure and thus, is the least likely nerve to be involved in this condition.</p>\n<p><strong>Highyeild:</strong></p><p>SUPERIOR ORBITAL FISSURE SYNDROME Superior orbital fissure syndrome is characterized by the compression of cranial nerves III, IV, and VI, and the ophthalmic division of V. The structures passing through the superior orbital fissure include cranial nerves III, IV, VI, the ophthalmic branch of V, and the superior ophthalmic vein.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Oculomotor Nerve passes through the superior orbital fissure; so it is involved in superior orbital fissure syndrome. Option: B. Ophthalmic division of the trigeminal nerve also passes through superior orbital fissure; so it is involved in superior orbital fissure syndrome. Option: D. Trochlear Nerve also passes through a superior orbital fissure; so it is involved in superior orbital fissure syndrome.</p>\n<p><strong>Extraedge:</strong></p><p>The superior orbital fissure lies between the greater and lesser wings of the sphenoid.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are a first-year ENT resident, you receive a patient complaining of a complete lack of smell for the past week. Which of the following will not be part of your differential diagnosis?", "options": [{"label": "A", "text": "Covid-19 Infection", "correct": false}, {"label": "B", "text": "Atrophic Rhinitis", "correct": false}, {"label": "C", "text": "Olfactory nerve injury", "correct": false}, {"label": "D", "text": "Allergic Rhinitis", "correct": true}], "correct_answer": "D. Allergic Rhinitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Allergic Rhinitis Anosmia refers to a complete loss of the sense of smell . Allergic rhinitis is more commonly associated with hyposmia , hence it'll not be a part of differential diagnosis. Anosmia only occurs in case it leads to nasal polyposis.</p>\n<p><strong>Highyeild:</strong></p><p>Causes of anosmia: Nasal obstruction due to nasal polyps, enlarged turbinates, or mucosal edema due to infections. Atrophic rhinitis, a degenerative disorder of nasal mucosa Peripheral neuritis (toxic or influenzal) Injury to olfactory nerves or olfactory bulb in fractures of anterior cranial fossa Intracranial lesions like abscesses, tumors, or meningitis, which cause pressure on olfactory tracts.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Covid 19 is associated with a complete lack of taste; that is anosmia. Option: B. Atrophic rhinitis is also associated with anosmia. Option: C. Olfactory nerve injury is also associated with anosmia.</p>\n<p><strong>Extraedge:</strong></p><p>COVID-19 is associated with a complete lack of taste and smell and is a common presenting symptom.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 14 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 3</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Recent Advances in Ent Surgeries & Therapies - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 3</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 3 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "The ENT ward has a case of Sudden Sensori-Neural Hearing Loss (SSNHL). After failed treatments with steroids, vasodilators, antiviral and anticoagulants, hyperbaric oxygen therapy (HBOT) is finally being tried at your tertiary medical centre. The following statements are true, except:", "options": [{"label": "A", "text": "The recommended treatment profile consists of 100% O2 at 0.5 atmospheres absolute for 90 min daily for 2-3 treatments.", "correct": true}, {"label": "B", "text": "Patients with moderate to profound SSNHL (≥ 41 dB) who present within 14 days of symptom onset should be considered for HBOT.", "correct": false}, {"label": "C", "text": "HBOT activates cell metabolism leading to the restoration of ionic balance and electrophysiological functions of the cochlea", "correct": false}, {"label": "D", "text": "Also used in CO poisoning, air embolism, thermal burns, etc", "correct": false}], "correct_answer": "A. The recommended treatment profile consists of 100% O2 at 0.5 atmospheres absolute for 90 min daily for 2-3 treatments.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>The recommended treatment profile consists of 100% O2 at 0.5 atmospheres absolute for 90 min daily for 2-3 treatments. The recommended treatment profile consists of 100% O2 at 2.0–2.5 atmospheres absolute for 90 min daily for 10–20 treatments. However, the optimal number of HBO treatments will vary, depending on the severity and duration of symptomatology and the response to treatment.</p>\n<p><strong>Highyeild:</strong></p><p>Hyperbaric oxygen therapy (HBOT) is a treatment modality involving the intermittent inhalation of 100% oxygen in chambers pressurised above 1 atmosphere absolute (ATA). HBOT has been used as an adjunctive therapy for sudden sensorineural hearing loss (SNHL). It raises the oxygen in the inner ear by diffusion, activating cell metabolism and restoring the cochlea's ionic balance and electrophysiological functions. Patients with moderate to profound SSNHL (≥ 41 dB) who present within 14 days of symptom onset should be considered for HBOT. The recommended treatment profile consists of 100% O2 at 2.0–2.5 atmospheres absolute for 90 min daily for 10–20 treatments.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. Patients with moderate to profound SSNHL (≥ 41 dB) who present within 14 days of symptom onset should be considered for HBOT a true statement. Option C. HBOT activates cell metabolism leading to restoration of ionic balance and electrophysiological functions of the cochlea is also a true statement. Option D. Also used in CO poisoning, air embolism, thermal burns, etc., is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The medical field is ever-evolving, and you have recently learnt about using radiowaves to reduce tissue volume surgically. You just assisted in a radiofrequency tonsillotomy surgery in the OT and have been to explain this new concept to the interns. All of the following statements are true, except:", "options": [{"label": "A", "text": "It is a minimally invasive technique.", "correct": false}, {"label": "B", "text": "Radiofrequency is used to perform tonsillotomy, micro laryngeal surgery, myringotomy, uvulopalatoplasty, correction of rhinophyma and cosmetic removal of skin lesions.", "correct": false}, {"label": "C", "text": "The temperature is controlled between 80 and 85 °C.", "correct": false}, {"label": "D", "text": "It is used to cut tissues without any coagulation.", "correct": true}], "correct_answer": "D. It is used to cut tissues without any coagulation.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>It is used to cut tissues without any coagulation. Radiofrequency (RF) cuts and coagulates tissues with minimal lateral tissue damage and charring.</p>\n<p><strong>Highyeild:</strong></p><p>Radiofrequency Surgery In Ent Radiowaves have been used surgically to reduce the volume of tissues. It has been used on inferior turbinates to relieve nasal obstruction; on the soft palate to relieve primary snoring, upper airway resistance and sleep apnoea; and on the base of the tongue to relieve sleep apnoea. It has also been used for the treatment of lingual thyroid. Radiofrequency (RF) cuts and coagulates tissues with minimal lateral tissue damage and charring. It is a minimally invasive technique, and surgery can be performed outdoors. Using different types of electrodes, radiofrequency has also been used to perform tonsillotomy, micro-laryngeal surgery (to remove granulomas, papillomas, cysts), myringotomy, uvulopalatoplasty, correction of rhinophyma and cosmetic removal of skin lesions. The radiofrequency (RF) device generates electromagnetic waves of very high frequency between 350 kHz and 4 MHz. Usually, 460 kHz is used. Usually, the temperature is controlled between 80 and 85 °C.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. It is a minimally invasive technique is a true statement. Option B. Using different types of electrodes, radiofrequency has also been used to perform tonsillotomy, micro-laryngeal surgery (to remove granulomas, papillomas, cysts), myringotomy, uvulopalatoplasty, correction of rhinophyma and cosmetic removal of skin lesions. Option C. Usually, the temperature is controlled between 80 and 85 °C.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You see the following patient in the ENT ward and question the PG about the treatment modality. He explains to you that it is brachytherapy. All of the following statements are true, except:", "options": [{"label": "A", "text": "Brachytherapy is a form of radiotherapy where a sealed radiation source is placed inside or close to the tumour", "correct": false}, {"label": "B", "text": "It can be used to provide a boost to external beam radiotherapy in early T1 or T2 tumours or to recurrences", "correct": false}, {"label": "C", "text": "Low-dose brachytherapy is the primary treatment modality for head and neck cancer.", "correct": true}, {"label": "D", "text": "Brachytherapy involves delivering radiation to the tumour via thin tubes called catheters", "correct": false}], "correct_answer": "C. Low-dose brachytherapy is the primary treatment modality for head and neck cancer.", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004721792-QTDE057004IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Low-dose brachytherapy is the primary treatment modality for head and neck cancer. Earlier, low-dose brachytherapy was delivered, but nowadays, high-dose brachytherapy is the primary treatment modality.</p>\n<p><strong>Highyeild:</strong></p><p>Brachytherapy In Head And Neck Cancer Brachytherapy is a form of radiotherapy where a sealed radiation source is placed inside or close to the tumour.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Brachytherapy is a form of radiotherapy where a sealed radiation source is placed inside or close to the tumour is a true statement. Option B. It can be used to boost external beam radiotherapy in early T1 or T2 tumours or to recurrences, which is also a true statement. Option D. Brachytherapy involves delivering radiation to the tumour via thin tubes called catheters is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 13 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 6</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Salivary Gland Tumours & Parotidectomy - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 6</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 6 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 35-year-old female patient presented with episodic flushing and sweating on her right cheek after eating spicy and sour food. She did not remember precisely when these complaints began and had a history of accidents and open surgery due to a mandibular condyle fracture. The right mandibular condyle was smaller than the left one in the chin radiograph. Based on these findings, what is the diagnosis of this patient?", "options": [{"label": "A", "text": "Pleomorphic Adenoma", "correct": false}, {"label": "B", "text": "Hemangiomas", "correct": false}, {"label": "C", "text": "Frey’s Syndrome", "correct": true}, {"label": "D", "text": "Acinic Cell Carcinoma", "correct": false}], "correct_answer": "C. Frey’s Syndrome", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Frey’s Syndrome Frey’s syndrome arises as a complication of parotid surgery, usually manifesting several months after the operation. It is characterised by sweating and flushing of the preauricular skin during mastication, causing a nuisance to the person or social embarrassment. It results from aberrant innervation of sweat glands by parasympathetic secretomotor fibres destined for the parotid. Instead of causing salivary secretion from the parotid, they cause secretion from the sweat glands.</p>\n<p><strong>Highyeild:</strong></p><p>The course of secretomotor fibres to the parotid: Inferior salivary nucleus → CN IX → Tympanic branch → Tympanic plexus → Lesser petrosal nerve → Otic ganglion → Auriculotemporal nerve → Parotid gland.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Pleomorphic adenoma is the most common benign tumor of the salivary gland. Usually arises from its tail. It is a slow-growing tumour and may be relatively larger at the initial period. It presents as a painless mass on one side of the face that gradually enlarges over time. Option: B. Hemangiomas are the most common benign parotid tumour in children, predominantly in males. They are soft and painless and increase in size with crying or straining. Overlying skin shows bluish discolouration. Option: D. is a low-grade tumour that appears similar to a benign mixed tumour. It is a small, firm, movable and encapsulated tumour, sometimes bilateral. Metastases are rare.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 73-year-old male patient presented with a case of painless firm fluctuant swelling of the left preauricular area of 8 months duration and has features of facial nerve palsy. The lesion was thoroughly examined preoperatively, and investigations were carried out. Histologically there were mucin-producing cells and squamous cells present. What is your probable diagnosis?", "options": [{"label": "A", "text": "Adenoid Cystic Carcinoma", "correct": false}, {"label": "B", "text": "Frey’s Syndrome", "correct": false}, {"label": "C", "text": "Acinic Cell Carcinoma", "correct": false}, {"label": "D", "text": "Mucoepidermoid Carcinoma", "correct": true}], "correct_answer": "D. Mucoepidermoid Carcinoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mucoepidermoid Carcinoma Generally, mucoepidermoid carcinoma is slow growing but can invade the facial nerve . Histologically, there are areas of mucin-producing cells and squamous cells, Called mucoepidermoid carcinoma.</p>\n<p><strong>Highyeild:</strong></p><p>MUCOEPIDERMOID CARCINOMA Generally, mucoepidermoid carcinoma is slow growing but can invade the facial nerve. Histologically, there are areas of mucin-producing and squamous cells, hence the name. The greater the epidermoid element, the more malignant the behaviour of the tumour. The tumours have been further classified as low-grade and high-grade. Low-grade tumours have a good prognosis (90% five years survival rate), and high-grade tumours are more aggressive and have a poor prognosis (30% five years survival rate). Low-grade tumours are more common in children.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Adenoid Cystic Carcinoma is a slow-growing tumour that widely infiltrates the tissue planes and muscles. It also invades perineural spaces and lymphatics and thus causes pain and VIIth nerve paralysis. It can metastasise to lymph nodes. Local recurrences after surgical excision are common and can occur as late as 10-20 years after surgery. Distant metastases go to the lung, brain and bone. Option: B. Frey’s syndrome arises as a complication of parotid surgery, usually manifesting several months after the operation. It is characterised by sweating and flushing of the preauricular skin during mastication, causing a nuisance to the person or social embarrassment. It results from aberrant innervation of sweat glands by parasympathetic secretomotor fibres destined for the parotid. Option: C. Acinic Cell Carcinoma is a low-grade tumour that resembles a benign mixed tumour. It is a small, firm, movable and encapsulated tumour, sometimes bilateral. Metastases are rare. A conservative approach of superficial or total parotidectomy is adopted.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 50-year-old female complained of painless swelling in the parotid region. It was smooth, non-tender, lobular, firm and mobile swelling on palpation. Histologically it shows both epithelial and mesenchymal elements. What is your diagnosis?", "options": [{"label": "A", "text": "Pleomorphic Adenoma.", "correct": true}, {"label": "B", "text": "Hemangiomas", "correct": false}, {"label": "C", "text": "Adenolymphoma", "correct": false}, {"label": "D", "text": "Oncocytoma", "correct": false}], "correct_answer": "A. Pleomorphic Adenoma.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Pleomorphic Adenoma. The above history and examination findings point towards the diagnosis of pleomorphic adenoma.</p>\n<p><strong>Highyeild:</strong></p><p>Pleomorphic adenoma Pleomorphic adenoma is the most common benign tumor of the salivary gland. Usually arises from its tail. It is a slow-growing tumor and may be relatively larger at the initial period. It presents as a painless mass on one side of the face that gradually enlarges over time.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Hemangiomas are Benign tumours of the parotid, usually in children. They are soft and painless and increase in size with crying or straining. The overlying skin may show bluish discoloration. Surgical excision is indicated if they do not regress spontaneously. Option: C. Adenolymphomas are commonly seen between the fifth and seventh decades with preponderance in males (5:1). They mainly involve the tail of the parotid. They are bilateral in 10% of the patients. They may be multiple. Adenolymphoma is a rounded, encapsulated tumour, at times cystic, with mucoid or brownish fluid. Histologically, epithelial and lymphoid elements are seen. Option: D. Oncocytoma arises from acidophilic cells called oncocytes and comprises less than 1% of all salivary gland tumours. Primarily seen in the elderly, they usually do not grow larger than 5 cm and involve the superficial lobe of the Benign oncocytomas are cystic rather than solid. Malignant oncocytomas are also seen.</p>\n<p><strong>Extraedge:</strong></p><p>They are called mixed tumours because both epithelial and mesenchymal elements are seen in histology. The stroma of the tumour may be mucoid, fibroid, vascular, myxo chondroid or chondroid, and its proportion to the epithelial element may vary. Although encapsulated, the tumour sends pseudopods into the surrounding gland, which are left behind if the tumour is simply shelled out.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 75-year-old female presented with a complaint of preauricular swelling for the past five months. On physical examination, the patient was found to have a soft left-sided mass measuring approximately 2 centimetres. Mild tenderness upon palpation of the mass was noted. Histologically there was a vascular acidophilic cell predominance. What type of treatment will you prefer?", "options": [{"label": "A", "text": "Superficial Parotidectomy", "correct": true}, {"label": "B", "text": "Deep Parotidectomy", "correct": false}, {"label": "C", "text": "Total Parotidectomy", "correct": false}, {"label": "D", "text": "Radiotherapy", "correct": false}], "correct_answer": "A. Superficial Parotidectomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Superficial Parotidectomy Since the diagnosis is oncocytoma and it is a benign condition. Treatment for parotid oncocytoma is superficial parotidectomy.</p>\n<p><strong>Highyeild:</strong></p><p>Parotid oncocytoma Oncocytoma arises from acidophilic cells called oncocytes, comprising less than 1% of all salivary gland tumours. Primarily seen in the elderly, they usually do not grow larger than 5 cm and involve the superficial lobe of the parotid. Benign oncocytomas are cystic rather than solid. Malignant oncocytomas are also seen. Treatment for parotid oncocytomas is also superficial parotidectomy.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Deep parotidectomy is usually preferred when there are metastases in the case of mucoepidermoid carcinoma; tumor excision depends on the tumour's location. Option: C. Total is preferred in case of high-grade malignant conditions of mucoepidermoid carcinoma, acinic cell carcinoma etc Option: D. Radiotherapy is used as postoperative treatment.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 60-year-old male patient complained of painful swelling in his right cheek, which has gradually increased its size over the past five months and has become ulcerated for the past five days. What is your diagnosis?", "options": [{"label": "A", "text": "Squamous Cell Carcinoma", "correct": true}, {"label": "B", "text": "Acinic Cell Carcinoma", "correct": false}, {"label": "C", "text": "Adenoid Cystic Carcinoma", "correct": false}, {"label": "D", "text": "Pleomorphic Adenoma", "correct": false}], "correct_answer": "A. Squamous Cell Carcinoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Squamous Cell Carcinoma Squamous Cell Carcinoma of parotid is a rapidly growing tumour that infiltrates, causes pain and ulcerates through the skin .</p>\n<p><strong>Highyeild:</strong></p><p>SQUAMOUS CELL CARCINOMA OF PAROTID Squamous Cell Carcinoma of parotid is a rapidly growing tumour that infiltrates, causes pain and ulcerates through the skin. It can metastasise to neck nodes. Radical parotidectomy may include muscle cuff or even a portion of the mandible, temporal bone and the involved skin. The radical neck is combined if nodal metastases are present. Surgery is followed by postoperative radiation to the primary site and the neck.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Acinic Cell Carcinoma is a low-grade tumour that resembles a benign mixed tumour. It is a small, firm, movable and encapsulated tumour, sometimes bilateral. Metastases are rare. Option: C. Adenoid Cystic Carcinoma is a slow-growing tumour that widely infiltrates the tissue planes and muscles. It also invades perineural spaces and lymphatics and thus causes pain and VIIth nerve paralysis. It can metastasise to lymph nodes. Local recurrences after surgical excision are common and can occur as late as 10-20 years after surgery. Distant metastases go to the lung, brain and bone. Option: D. Pleomorphic adenoma is the most common benign tumor of the salivary gland. Usually arises from its tail. It is a slow-growing tumor and may be relatively larger at the initial period. It presents as a painless mass on one side of the face that gradually enlarges over time.</p>\n<p><strong>Extraedge:</strong></p><p>Squamous cell carcinoma of the right parotid. Patient presented with a parotid swelling (A) and facial palsy (B).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 21-year-old man presented with a history of swelling in his lower right back teeth region for three months. The swelling was sudden in onset and had gradually grown to its present size. The patient had an associated history of pain that was sudden in the beginning, dull and continuous, and worse while eating food for the past ten days. On intraoral examination, a solitary well-defined oval-shaped erythematous swelling of size 1.0×0.8 cm was noticed in the right retromolar area. The swelling was soft to firm and was tender on palpation. A tissue sample was submitted for histopathological evaluation. An H&E-stained microscopic tissue section showed areas of mucin-producing cells and squamous cells. What would be your diagnosis?", "options": [{"label": "A", "text": "Mucoepidermoid Carcinoma", "correct": true}, {"label": "B", "text": "Adenoid Cystic Carcinoma", "correct": false}, {"label": "C", "text": "Acinic Cell Carcinoma", "correct": false}, {"label": "D", "text": "Squamous Cell Carcinoma", "correct": false}], "correct_answer": "A. Mucoepidermoid Carcinoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mucoepidermoid Carcinoma Mucoepidermoid Carcinoma is slow growing but can invade the facial nerve. Histologically, there are areas of mucin-producing and squamous cells, hence the name.</p>\n<p><strong>Highyeild:</strong></p><p>MUCOEPIDERMOID CARCINOMA It is slow growing but can invade the facial nerve. Histologically, there are areas of mucin-producing and squamous cells, hence the name. The greater the epidermoid element, the more malignant the behaviour of the tumour. The tumours have been further classified as low-grade and high-grade. Low-grade tumours have a good prognosis (90% five years survival rate), and high-grade tumours are more aggressive and have a poor prognosis (30% five years survival rate). Low-grade tumours are more common in children. The behaviour of mucoepidermoid tumours of minor Salivary glands is more aggressive and akin to adenoid cystic carcinoma, but in the major salivary glands, they behave like pleomorphic adenoma. Low-grade parotid tumours are treated by superficial or total parotidectomy, depending on the tumour's location. The facial nerve is preserved. High-grade tumours being more aggressive, are treated by total parotidectomy. The facial nerve may be sacrificed if invaded by the tumour. Some surgeons also combine radical neck dissection because of a high incidence of microscopic spread of the tumour.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. ADENOID CYSTIC CARCINOMA is a slow-growing tumour that widely infiltrates the tissue planes and muscles. It also invades perineural spaces and lymphatics and thus causes pain and VIIth nerve paralysis. It can metastasise to lymph nodes. Local recurrences after surgical excision are common and can occur as late as 10-20 years after surgery. Distant metastases go to the lung, brain and bone. Option: C. ACINIC CELL CARCINOMA is a low-grade tumour that resembles a benign mixed tumour. It is a small, firm, movable and encapsulated tumour, sometimes bilateral. Metastases are rare. A conservative approach of superficial or total parotidectomy is adopted. Option: D. SQUAMOUS CELL CARCINOMA is a rapidly growing tumour that infiltrates, causes pain and ulcerates through the skin. It can metastasise to neck nodes. Radical parotidectomy may include a cuff of muscle or even a portion of the mandible, temporal bone and the involved skin. The radical neck is combined if nodal metastases are present. Surgery is followed by postoperative radiation to the primary site and the neck.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 16 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 7</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Serous Otitis Media (Som) - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 7</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 7 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Otitis externa hemorrhagia is caused by:", "options": [{"label": "A", "text": "Influenza", "correct": true}, {"label": "B", "text": "Proteus", "correct": false}, {"label": "C", "text": "Staphylococcus", "correct": false}, {"label": "D", "text": "Streptococcus", "correct": false}], "correct_answer": "A. Influenza", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Influenza Otitis externa hemorrhagia is caused by the influenza virus.</p>\n<p><strong>Highyeild:</strong></p><p>OTITIS EXTERNA HEMORRHAGICA Otitis externa is haemorrhagic and is characterized by the formation of haemorrhagic bullae on the tympanic membrane and deep meatus. Viral in origin and may be seen in influenza epidemics. It causes severe pain in the ear and blood-stained discharge when the bullae rupture. Treatment with analgesics is directed to give relief from pain. Antibiotics are given for secondary infection of the ear canal, or middle ear if the bulla has ruptured into the middle ear.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All of the following are true about serous otitis media except?", "options": [{"label": "A", "text": "Sterile effusion of the middle ear due to respiratory tract infection", "correct": false}, {"label": "B", "text": "The most common cause of childhood hearing loss", "correct": false}, {"label": "C", "text": "Loss of light reflex", "correct": false}, {"label": "D", "text": "Marked congestion of the tympanic membrane", "correct": true}], "correct_answer": "D. Marked congestion of the tympanic membrane", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Marked congestion of the tympanic membrane In serous otitis media Tympanic membrane is often dull and opaque with a loss of light reflex . It may appear yellow, grey or bluish in colour. A thin leash of blood vessels may be seen along the handle of the malleus or at the periphery of the tympanic membrane and differs from marked congestion of acute suppurative otitis media.</p>\n<p><strong>Highyeild:</strong></p><p>Otoscopic findings in serous otitis media: Fluid level and air bubbles are seen when fluid is thin and the tympanic membrane transparent. In chronic cases, the fluid becomes very sticky. Hence, the name glue ear. The tympanic membrane is often dull and opaque with a loss of light reflex. Mobility of the tympanic membrane is restricted.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- It is an insidious condition characterized by the accumulation of nonpurulent effusion in the middle ear cleft. Often the effusion is thick and viscid but sometimes it may be thin and serous. The fluid is nearly sterile. Option B- It is the most common cause of childhood hearing loss Option C- In serous otitis media Tympanic membrane is often dull and opaque with a loss of light reflex.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Grommet tube is used in:", "options": [{"label": "A", "text": "Secretory Otitis Media", "correct": false}, {"label": "B", "text": "Mucoid Otitis Media", "correct": false}, {"label": "C", "text": "Serous Otitis Media", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above Myringotomy is coupled with grommet tube insertion in Serous otitis media (also k/a mucoid otitis media/glue ear/secretory otitis media) Adhesive otitis media Recurrent acute otitis media</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The treatment of choice for glue ears is", "options": [{"label": "A", "text": "Myringotomy with a cold knife", "correct": false}, {"label": "B", "text": "Myringotomy with diode laser", "correct": false}, {"label": "C", "text": "Myringotomy with ventilation tube insertion", "correct": true}, {"label": "D", "text": "Conservative treatment with analgesics and antibiotics", "correct": false}], "correct_answer": "C. Myringotomy with ventilation tube insertion", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Myringotomy with ventilation tube insertion The treatment of choice for glue ear is the insertion of a grommet (i.e., ventilation tube insertion).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option A - Performing uvulopalatoplasty (UPP) surgically with a cold knife or assisted with radiofrequency (RAUP) or laser (LAUP) is done in snoring treatment. Option B - Diode lasers have been used in turbinate reduction, laser-assisted stapedectomy and mucosa intact tonsillar ablation. Option D - Conservative treatment with analgesics and antibiotics is the initial treatment and if it fails myringotomy and aspiration of fluid are done.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In serous otitis media one of the. following statements is true?", "options": [{"label": "A", "text": "Sensorineural deafness occurs as a complication in 80% of the cases", "correct": false}, {"label": "B", "text": "Intracranial spread of the infection complicates the clinical courses", "correct": false}, {"label": "C", "text": "Tympanostomy tubes are usually required for treatment", "correct": true}, {"label": "D", "text": "Gram-positive Organisms are grown routinely in culture in the aspirate", "correct": false}], "correct_answer": "C. Tympanostomy tubes are usually required for treatment", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tympanostomy tubes are usually required for treatment Tympanostomy tubes are usually required for treatment. This is correct because if medical management fails , the best treatment is myringotomy with the insertion of a Tympanostomy tube (Grommet or ventilation tube).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option A- Sensorineural hearing deafness occurs as a complication in 80% of cases. This is not correct because serous otitis media leads to the conductive type of hearing loss. Option B - Intracranial spread of the infection complicates the clinical course. This is not true as complications of serous otitis media are - Adhesive otitis media Atrophy of the tympanic membrane Tympanosclerosis (chalky white deposits seen on the membrane) Atelectasis of the middle ear Ossicular necrosis Cholesteatoma due to retraction pockets Cholesterol granuloma due to stasis of secretions Option D - Gram-positive organism is grown routinely in culture in the aspirate is absolutely incorrect because fluid collection in serous otitis media is sterile</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 16-year-old girl was brought in due to hearing loss and a sense of fullness in the ear. Her parents report that she has had a recurrent history of earache and occasional purulent discharge when she went swimming with her friends but it mostly subsided in a few days without taking medications. The parents said this was all thanks to her powerful immunity after eating herbal medicine from her aunt for the last 8 years. On exam, you discover the presence of secretory otitis media with effusion secondary to previous unresolved infections. Which one of the following statements is true?", "options": [{"label": "A", "text": "Sensorineural deafness occurs as a complication in 80% of the cases", "correct": false}, {"label": "B", "text": "Intracranial spread of the infection complicates the clinical courses", "correct": false}, {"label": "C", "text": "Tympanostomy tubes are usually required for treatment", "correct": true}, {"label": "D", "text": "Gram-positive organisms are grown routinely in culture in the aspirate", "correct": false}], "correct_answer": "C. Tympanostomy tubes are usually required for treatment", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tympanostomy tubes are usually required for treatment Tympanostomy tubes are usually required for treatment. This is quite correct as myringotomy and aspiration of middle ear effusion without a ventilation tube/Tympanostomy tube/ grommet insertion has a short-lived benefit and is not recommended . Hence if otitis media with effusion in serous OM is not resolved spontaneously, a tympanostomy tube is inserted.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option A- Sensorineural deafness occurs as a complication in 80% of cases This is not correct because serous otitis media leads to conductive type of hearing loss. Option B- Intracranial spread of the infection complicates the clinical course. This is not true as complications of serous otitis media are- Adhesive otitis media Atrophy of the tympanic membrane Tympanosclerosis (chalky white deposits seen on. membrane) Atelectasis of the middle ear Ossicular necrosis Cholesteatoma due to retraction pockets Cholesterol granuloma due to stasis of secretions Option D- Gram-positive organisms are grown routinely in culture in the aspirate is absolutely incorrect because fluid collection in serous otitis media is sterile.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The commonest cause of hearing loss in children is", "options": [{"label": "A", "text": "Microtia with atresia of the external auditory meatus", "correct": false}, {"label": "B", "text": "Trauma", "correct": false}, {"label": "C", "text": "Otitis media with effusion", "correct": true}, {"label": "D", "text": "Tm Perforation", "correct": false}], "correct_answer": "C. Otitis media with effusion", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Otitis media with effusion The most common cause of conductive deafness in children is otitis media with effusion , which is typically of mild to moderate severity.</p>\n<p><strong>Highyeild:</strong></p><p>Otitis media with effusion/glue ear/chronic serous or secretory otitis media It is the most common cause of hearing loss in children in the developed world and has a peak incidence at 2 and 5 years of age. Otoscopic findings in serous otitis media: Fluid level and air bubbles are seen when fluid is thin and the tympanic membrane transparent. In chronic cases, the fluid becomes very sticky. Hence, the name glue ear. The tympanic membrane is often dull and opaque with a loss of light reflex. Mobility of the tympanic membrane is restricted.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 17 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 4</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Speech Rehabilitation - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 4</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 4 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 47-year-old patient came to the ENT OPD with complaints of difficulty in swallowing, hoarseness of voice, and long-lasting cough. Various investigations were done, and it was found out to be carcinoma larynx. A permanent tracheostomy was done in this patient after a total laryngectomy. Now the patient was concerned about his voice. Which of the following options is correct for voice rehabilitation options in this patient after a permanent tracheostomy?", "options": [{"label": "A", "text": "Muir Valve", "correct": false}, {"label": "B", "text": "Esophageal Speech", "correct": false}, {"label": "C", "text": "Tracheoesophageal Speech", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above The correct answer is all of the above.</p>\n<p><strong>Highyeild:</strong></p><p>Various ways for voice rehabilitation after permanent tracheostomy are 1. Phonosurgeey 2. Muir valve 3. Esophageal speech 4. Tracheoesophageal speech 5. Electrolarynx 6. Transoral pneumatic device</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following option is incorrect regarding the image given below?", "options": [{"label": "A", "text": "Used in tracheostomy with normal larynx for speech rehabilitation", "correct": false}, {"label": "B", "text": "It allows air to go in and doesn’t allow air to come out.", "correct": false}, {"label": "C", "text": "Patient cannot speak for a longer duration", "correct": true}, {"label": "D", "text": "The valve has a diaphragm", "correct": false}], "correct_answer": "C. Patient cannot speak for a longer duration", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004183541-QTDE071002IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Patient cannot speak for a longer duration The given image is of the Passy Muir valve. The patient can speak longer There is no need to close the opening.</p>\n<p><strong>Highyeild:</strong></p><p>PASSY MUIR VALVE It has the following features. 1. It is used in tracheostomy with normal larynx for speech rehabilitation 2. It only allows air to go in and doesn’t allow air to come out 3. Its valve has a diaphragm 4. Patient can speak longer, and there is no need to close the opening.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. It is used in tracheostomy with normal larynx for speech rehabilitation is a true statement. Option: B. It only allows air to go in and doesn’t allow air to come out Option: D. It’s valve has a diaphragm</p>\n<p><strong>Extraedge:</strong></p><p>Humidity can be used with the valve in place. Oxygen can be given with the valve in place Remove the valve during aerosol treatment. If it is left on, remove it and rinse it to remove any medications that could cause the valve to stick or not work well.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following options are incorrect regarding the disadvantages of the image given below?", "options": [{"label": "A", "text": "Valve can get blocked easily", "correct": false}, {"label": "B", "text": "Cleaning Of the diaphragm is required at regular intervals.", "correct": false}, {"label": "C", "text": "Can be used along with the cuffed tube.", "correct": true}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "C. Can be used along with the cuffed tube.", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004184904-QTDE071003IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Can be used along with the cuffed tube. The given image is of the Passy Muir valve. It cannot be used along with the cuffed tube.</p>\n<p><strong>Highyeild:</strong></p><p>Various disadvantages of the Passy Muir valve are as follows. 1. Its valve can get blocked easily 2. Cleaning of the diaphragm is required at regular intervals 3. It cannot be used along with cuffed tube</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Its valve can get blocked easily is a true statement. Option: B. Cleaning of the diaphragm is required at regular intervals is also a true statement.</p>\n<p><strong>Extraedge:</strong></p><p>Humidity can be used with the valve in place. Oxygen can be given with the valve in place Remove the valve during aerosol treatment. If it is left on, remove it and rinse it to remove any medications that could cause the valve to stick or not work well.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 52 year old patient underwent a tracheostomy procedure last week as indicated for the disease he suffered with. He was concerned for his voice. So for his speech rehabilitation doctors prescribed him passy Muir valve. For the proper care of the valve, doctors told him some precautions. Which of the following options is incorrect for properly caring for the valve?", "options": [{"label": "A", "text": "Clean the valve daily with mild soapy water.", "correct": false}, {"label": "B", "text": "Rinse the valve with cold water and not hot water", "correct": false}, {"label": "C", "text": "Clean the valve with alcohol or peroxide solution", "correct": true}, {"label": "D", "text": "Replace the valve when it becomes sticky.", "correct": false}], "correct_answer": "C. Clean the valve with alcohol or peroxide solution", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Clean the valve with alcohol or peroxide solution Do not use a brush, vinegar, peroxide, bleach, or alcohol on the valve.</p>\n<p><strong>Highyeild:</strong></p><p>The precautions must be taken for the proper care of the Passy Muir valve. 1. Clean the valve daily with mild soapy water 2. Rinse with cool to warm water. Hot water may damage the valve 3. Let the valve air dry completely before using it again 4. Do not use a brush, vinegar, peroxide, bleach, or alcohol on the valve 5. Replace the valve when it becomes sticky, noisy, or vibrates.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Clean the valve daily with mild soapy water is a true statement. Option: B. Rinse with cool to warm water. Hot water may damage the valve. Option: D. Replace the valve when it becomes sticky is a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 14 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 5</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Surgeries Of Otitis Media - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 5</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 5 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A 45-year-old patient with six months of non-foul-smelling ear discharge and hearing loss. The TM appearance is given below. Treatment includes all except:", "options": [{"label": "A", "text": "Topical Antibiotics", "correct": false}, {"label": "B", "text": "Systemic Antibiotics", "correct": false}, {"label": "C", "text": "Mastoidectomy", "correct": true}, {"label": "D", "text": "Tympanoplasty", "correct": false}], "correct_answer": "C. Mastoidectomy", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004069060-QTDE074002IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Mastoidectomy Non-foul smelling discharge of 6 months suggests that the above is a safe CSOM . Hence management is car toileting, local and systemic antibiotics , and surgical repair by tympanoplasty once the ear is dry for six weeks without antibiotics .</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient comes to the hospital. You observe the following finding in otoscopy in patient who has been treated for the disease in the middle ear. What is the device shown here:", "options": [{"label": "A", "text": "Partial Ossicular Replacement Prosthesis", "correct": false}, {"label": "B", "text": "Total Ossicular Replacement Prosthesis", "correct": false}, {"label": "C", "text": "Piston", "correct": false}, {"label": "D", "text": "Grommet", "correct": true}], "correct_answer": "D. Grommet", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004071595-QTDE074003IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Grommet The device shown here is a grommet , a tympanostomy or a ventilation tube. It is placed in the middle ear to aerate and drain the middle ear . It acts as a substitute for the blocked eustachian tube. It is placed through radial myringotomy in the anteroinferior quadrant.</p>\n<p><strong>Highyeild:</strong></p><p>GROMMET If myringotomy and aspiration combined with medical measures have not helped and fluid recurs, a grommet is inserted to continue aeration of the middle ear. It is left in place for weeks or months or till it is spontaneously extruded. It is done in otitis media with effusion.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 30-year-old male has attic cholesteatoma of the left ear with lateral sinus thrombophlebitis. Which of the following will be the operation of choice?", "options": [{"label": "A", "text": "An intact canal wall mastoidectomy", "correct": false}, {"label": "B", "text": "Simple mastoidectomy with tympanoplasty", "correct": false}, {"label": "C", "text": "Canal wall down mastoidectomy", "correct": true}, {"label": "D", "text": "Mastoidectomy with cavity obliteration", "correct": false}], "correct_answer": "C. Canal wall down mastoidectomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Canal wall down mastoidectomy The aim of surgery in middle ear infections is to make the ear safe. Since attic cholesteatoma, in this case, is complicated by lateral sinus thrombosis, modified radical mastoidectomy (canal wall down procedure) will be justified. In this case, the disease will be exteriorised so the cavity can be subsequently examined and cleaned if a cholesteatoma is left behind or recurs. Intact canal wall mastoidectomy and simple mastoidectomy are \"canal wall up\" procedures. Even after removing the cholesteatoma, obliteration of the mastoid cavity runs the risk of burying the cholesteatoma and subsequent complications.</p>\n<p><strong>Highyeild:</strong></p><p>COMPARISON OF CANAL WALL UP AND CANAL WALL DOWN PROCEDURES Canal wall up procedure Canal wall down procedure Meatus Normal appearance Widely open meatus communicating with mastoid Dependence Does not require routine cleaning Dependence on doctor for cleaning mastoid cavity once or twice a year Recurrence or residual disease High rate of recurrent or residual cholesteatoma Low rate of recurrence or residual disease and thus a safe procedure Second look surgery Requires second look surgery after 6 months or so to rule out cholesteatoma Not required Patients limitations No limitation. Patient allowed swimming Swimming can lead to infection of mastoid cavity and it is thus curtailed Auditory rehabilitation Easy to wear a hearing aid if needed Problems in fitting a hearing aid due to large meatus and mastoid cavity which sometimes gets infected</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In a 20-year-old patient following type of incision is given on the tympanic membrane. Identify the condition in which this type of incision is given.", "options": [{"label": "A", "text": "Asom", "correct": true}, {"label": "B", "text": "Safe Csom", "correct": false}, {"label": "C", "text": "Unsafe Csom", "correct": false}, {"label": "D", "text": "SOM", "correct": false}], "correct_answer": "A. Asom", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004072357-QTDE074005IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Asom In acute suppurative otitis media, first-line treatment is medical, and myringotomy is done when the ear is dry.</p>\n<p><strong>Highyeild:</strong></p><p>MYRINGOTOMY It is a curvilinear (J-shaped) incision on the drum to evacuate pus and is indicated when (i) Drum is bulging, and there is acute pain, (ii) There is an incomplete resolution despite antibiotics when the drum remains complete with persistent conductive hearing loss. (iii) There is persistent effusion beyond 12 weeks. All acute suppurative otitis media cases should be carefully followed until the tympanic membrane returns to normal appearance and conductive hearing loss disappear.</p>\n<p><strong>Extraedge:</strong></p><p>Myringotomy is never done in the posterosuperior quadrant because of the chance of injury to Chorda tympani Facial nerve Incudostapedial joint Oval window</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The incision used in the endo meatal approach to the ear is?", "options": [{"label": "A", "text": "Lempert I Incision", "correct": false}, {"label": "B", "text": "Rosen’s Incision", "correct": true}, {"label": "C", "text": "Lempert II Incision", "correct": false}, {"label": "D", "text": "Wilde’s Incision", "correct": false}], "correct_answer": "B. Rosen’s Incision", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rosen’s Incision The endomeatal or transcanal approach raises a tympanomeatal flap to expose the middle ear. Rosen's incision is the most commonly used for stapedectomy.</p>\n<p><strong>Highyeild:</strong></p><p>The endomeatal or transcanal approach raises a tympanomeatal flap to expose the middle ear. Rosen's incision is the most commonly used for stapedectomy. It requires the meatus and canal to be wide enough to work. It consists of two parts: (i) a small vertical incision at the 12 o’clock position near the annulus and (ii) a curvilinear incision starting at 6 o’clock position to meet the first incision in the posterosuperior region of the canals, 5–7 mm away from the annulus. Rosen’s incision for tympanotomy.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Lempert I is a semicircular incision from 12 o’clock to 6 o’clock in the posterior meatal wall at the bony–cartilaginous junction. Option: C. Lempert II Starts from the first incision at 12 o’clock and then passes upwards in a curvilinear fashion between the tragus and the crus of the It passes through the incisura terminalis and thus does not cut the cartilage. Option: D. Wilde’s incision starts at the highest attachment of the pinna, follows the curve of the retro auricular groove, lying 1 cm behind it, and ends at the mastoid tip.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 15 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 6</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Surgeries for Rhinosinusitis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 6</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 6 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "This is an endoscopic Image of a patient who had an endoscopic sinus surgery previously. Currently he is well and has no nasal symptom:", "options": [{"label": "A", "text": "Synechiae", "correct": true}, {"label": "B", "text": "Septal Spur", "correct": false}, {"label": "C", "text": "Haemangioma", "correct": false}, {"label": "D", "text": "Septal Perforation", "correct": false}], "correct_answer": "A. Synechiae", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004363690-QTDE068002IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Synechiae Nasal synechiae is an adhesion formation between the nasal septum and turbinate’s by scar tissue is often the result of injury to opposing surfaces of nasal mucosa. It can result from intranasal operations such as septal surgery, polypectomy removal of foreign bodies, reduction of nasal fractures, endoscopic sinus surgery or even intranasal packing.</p>\n<p><strong>Highyeild:</strong></p><p>Nasal synechia left.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Surgical techniques used for the management of Chronic Maxillary sinusitis are all except:", "options": [{"label": "A", "text": "Antral Puncture", "correct": false}, {"label": "B", "text": "Intranasal Antrostomy", "correct": false}, {"label": "C", "text": "Howarth’s Operation", "correct": true}, {"label": "D", "text": "Caldwell Luc Operation", "correct": false}], "correct_answer": "C. Howarth’s Operation", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Howarth’s Operation Howarths operation is a surgical treatment for chronic frontal sinusitis not for chronic maxillary sinusitis. In howarth’s / Lynch operation the frontal sinus is entered through its floor by a curvilinear incision round the inner margin of the orbit. Diseased mucosa is removed, ethmoid cells exenterated and a new frontonasal duct created.</p>\n<p><strong>Highyeild:</strong></p><p>Surgeries of CHRONIC MAXILLARY SINUSITIS Antral puncture and irrigation - Sinus cavity is irrigated with a cannula passed through the inferior meatus. Removal of pus and exudates helps the sinus mucosa to revert to normal. Intranasal antrostomy - It is indicated if sinus irrigations fail to resolve infection. A window is created in the inferior meatus to provide aeration to the sinus and its free drainage. Caldwell–Luc operation - In this operation, antrum is entered through its anterior wall by a sublabial incision. All irreversible diseases are removed and a window is created between the antrum and inferior meatus.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In Antral puncture and irrigation Sinus cavity is irrigated with a cannula passed through the inferior meatus. Removal of pus and exudates helps the sinus mucosa to revert to normal. Option: B. Intranasal antrostomy is indicated if sinus irrigations fail to resolve infection. A window is created in the inferior meatus to provide aeration to the sinus and its free drainage. Option: D. In Caldwell–Luc operation antrum is entered through its anterior wall by a sublabial incision. All irreversible diseases are removed and a window is created between the antrum and inferior meatus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient with chronic rhinosinusitis is scheduled to undergo endoscopic sinus surgery. Apart from rhinosinusitis, all the given conditions below are also indications of this surgery, except:", "options": [{"label": "A", "text": "Control of Epistaxis", "correct": false}, {"label": "B", "text": "Antrochoanal Polyp", "correct": false}, {"label": "C", "text": "Removal of foreign body from the nose", "correct": false}, {"label": "D", "text": "Lateral frontal sinus disease", "correct": true}], "correct_answer": "D. Lateral frontal sinus disease", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lateral frontal sinus disease Lateral frontal sinus disease is a contraindication to endoscopic sinus surgery due to inaccessibility.</p>\n<p><strong>Highyeild:</strong></p><p>INDICATIONS for endoscopic sinus surgery are: Chronic bacterial sinusitis unresponsive to adequate medical treatment. Recurrent acute bacterial sinusitis. Polypoid rhinosinusitis (diffuse nasal polyposis). Fungal sinusitis with fungal ball or nasal polypi. Control of epistaxis by endoscopic cautery. Mucocele of frontoethmoid or sphenoid sinus. Antrochoanal polyp. Removal of foreign body from the nose or sinus. Endoscopic septoplasty.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Epistaxis control is an indication of endoscopic sinus surgery. Option: B. Antrochoanal polyp is an indication of endoscopic sinus surgery. Option: C. Removal of foreign body from the nose can also be done endoscopically.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A middle-aged patient with rhinosinusitis wants to opt for endoscopic sinus surgery. As the ENT PG assigned to the ward, you need to explain to him why its not a feasible option in his case. All of the following are contraindications to endoscopic sinus surgery, except:", "options": [{"label": "A", "text": "Osteomyelitis", "correct": false}, {"label": "B", "text": "Epistaxis", "correct": true}, {"label": "C", "text": "Lateral frontal sinus disease", "correct": false}, {"label": "D", "text": "Stenosis of internal opening of frontal sinus", "correct": false}], "correct_answer": "B. Epistaxis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Epistaxis Control of epistaxis by endoscopic cautery is an indication for endoscopic sinus surgery ; not a contraindication.</p>\n<p><strong>Highyeild:</strong></p><p>Contraindication to endoscopic sinus surgery: Inexperience and lack of proper instrumentation. Disease inaccessible by endoscopic procedures, e.g. lateral frontal sinus disease and stenosis of internal opening of frontal sinus. Osteomyelitis. Threatened intracranial or intraorbital complication.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Osteomyelitis is a Contraindication to endoscopic sinus surgery. Option: C. lateral frontal sinus disease is also a Contraindication to endoscopic sinus surgery. Option: D. stenosis of internal opening of frontal sinus is also a Contraindication to endoscopic sinus surgery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Advantages of endoscopic sinus surgery over the conventional surgeries are all, except:", "options": [{"label": "A", "text": "Does not require skin incisions", "correct": false}, {"label": "B", "text": "Does not preserve function of mucociliary clearance", "correct": true}, {"label": "C", "text": "Does not require removal of intervening bone to access the disease", "correct": false}, {"label": "D", "text": "Does not destroy nasal and sinus mucosa", "correct": false}], "correct_answer": "B. Does not preserve function of mucociliary clearance", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Does not preserve function of mucociliary clearance Endoscopic sinus surgery preserves the function of mucociliary clearance. It is one of the advantages of endoscopic sinus surgery over conventional surgeries.</p>\n<p><strong>Highyeild:</strong></p><p>ENDOSCOPIC SINUS SURGERY Endoscopic surgery has made a great contribution towards management of sinus disease. Indications for conventional operations like those of Caldwell–Luc, frontal sinus operations and external ethmoidectomy have greatly reduced. Endoscopic surgery is minimally invasive surgery and does not require skin incisions or removal of intervening bone to access the disease. In the sinuses, ventilation and drainage of the sinuses is established, preserving the nasal and sinus mucosa and its function of mucociliary clearance ; that is why it is called functional Sx (FESS)</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A. Endoscopic surgery is minimally invasive surgery and does not require skin incisions . Option: C. Endoscopic surgery is minimally invasive surgery and does not require removal of intervening bone to access the disease. Option: D. In the sinuses, ventilation and drainage of the sinuses is established, preserving the nasal and sinus mucosa</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Patient presents to your OPD with complaints of Acute Frontal sinusitis. Despite medical management, there is exacerbation of pain and pyrexia. The next step in management is:", "options": [{"label": "A", "text": "Antral Lavage", "correct": false}, {"label": "B", "text": "Dacrocystorhinostomy", "correct": false}, {"label": "C", "text": "Trephination of frontal sinus", "correct": true}, {"label": "D", "text": "Antrostomy", "correct": false}], "correct_answer": "C. Trephination of frontal sinus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Trephination of frontal sinus If there is persistence or exacerbation of pain or pyrexia in spite of medical treatment for 48 h, or if the lid swelling is increasing and threatening orbital cellulitis, frontal sinus is drained externally called as trephination of frontal sinus.</p>\n<p><strong>Highyeild:</strong></p><p>Trephination of frontal sinus If there is persistence or exacerbation of pain or pyrexia in spite of medical treatment for 48 h, or if the lid swelling is increasing and threatening orbital cellulitis, frontal sinus is drained externally. A 2 cm long horizontal incision is made in the superomedial aspect of the orbit below the eyebrow . Floor of the frontal sinus is exposed and a hole drilled with a burr. Pus is taken for culture and sensitivity, and a plastic tube inserted and fixed. Sinus can now be irrigated with normal saline two or three times daily until the frontonasal duct becomes patent. This can be determined by adding a few drops of methylene blue to the irrigating fluid and its exit seen through the nose. Drainage tube is removed when the frontonasal duct becomes patent.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A. Antral lavage. Most cases of acute maxillary sinusitis respond to medical treatment. Lavage is rarely necessary. It is done only when medical treatment has failed and that too only under cover of antibiotics. Option: B. Dacryocystorhinostomy (DCR) surgery is a procedure that aims to eliminate fluid and mucus retention within the lacrimal sac, and to increase tear drainage for relief of epiphora (water running down the face). Option: D. Maxillary antrostomy is a surgical procedure to enlarge the opening (ostium) of the maxillary sinus.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 16 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 9</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Tracheostomy Tubes - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 9</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 9 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A tracheostomised patient, with a Portex tracheostomy tube, in the ward developed a sudden complete blockage of the tube. Which of the following is the best next step in the management:", "options": [{"label": "A", "text": "Immediate removal of the tracheostomy tube", "correct": true}, {"label": "B", "text": "Suction of tube with sodium bicarbonate", "correct": false}, {"label": "C", "text": "Suction of tube with saline", "correct": false}, {"label": "D", "text": "Transtracheal Jet Ventilation", "correct": false}], "correct_answer": "A. Immediate removal of the tracheostomy tube", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Immediate removal of the tracheostomy tube Portex is a PVC tracheostomy tube . In case of a complete blockade, the tube must be changed immediately.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Suction with sodium bicarbonate is frequently done in a tracheotomised patient as a preventive measure to avoid obstruction. Option: C. Suction with saline is frequently done in a tracheotomised patient as a preventive measure to avoid obstruction. Option: D. Transtracheal jet ventilation (PTJV: Percutaneous transtracheal jet ventilation) is an emergency technique for providing respiration. This is done by doing a needle Cricothyrotomy (i.e. piercing the cricothyroid membrane). It is a simple, fast and safe access at times during emergencies when endotracheal intubation or other ventilation methods are not feasible.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 7-year-old boy was referred to a local private hospital with a low-grade fever, cough and sore throat for 5 days. He subsequently became worse and experienced neck swelling, dyspnoea and dysphagia. He also had a harsh breathing sound. On the fifth day, his vital signs showed a blood pressure of 110/90 mm hg. On examination, his tonsils were inflamed and had white patches, and he had inspiratory stridor and poor air entry but no adventitious sounds. Then tracheostomy was done to remove the obstruction and secretions. Which type of tracheostomy tube is used for decannulation in children?", "options": [{"label": "A", "text": "Fenestrated Tube", "correct": true}, {"label": "B", "text": "Double-Cuffed Tube", "correct": false}, {"label": "C", "text": "Flange Long Tube", "correct": false}, {"label": "D", "text": "Double Lumen Tube", "correct": false}], "correct_answer": "A. Fenestrated Tube", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Fenestrated Tube Decannulation rates are higher in children with airway obstruction than in children requiring prolonged ventilation and have decreased as the indications for tracheostomy have evolved. A fenestrated tube provides adequate space for the start of spontaneous ventilation – so this tube is helpful in decannulation.</p>\n<p><strong>Highyeild:</strong></p><p>Fenestrated tube single or multiple holes are situated at the upper curvature. The hole helps in speech production or in weaning from tracheostomy. The fenestrated tube is used in children for decannulation. Decannulation rates are higher in children with airway obstruction than in children requiring prolonged ventilation and have decreased as the indications for tracheostomy have evolved. A fenestrated tube provides adequate space for the start of spontaneous ventilation – so this tube is helpful in decannulation.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Each cuff can be inflated alternately to prevent pressure necrosis at one site in a double cuff tube. Uncuffed endotracheal tubes are used for children <8 years old. Because the cricoid is the narrowest part of the airway, cuffs are unnecessary and may lead to tracheal stenosis. However, modern high-volume/low-pressure cuffs have become accepted by children. Option: C. In the long flange tube, extra-length tracheostomy tubes are used when pre-tracheal tissues are thick or swollen or pass a growth or stenosis in traches. Flange, in these cases, is movable and fixed at a desired place according to the thickness of the tissues of the neck. Option: D. In a double-lumen tube, they have an inner cannula inside an outer cannula. It is easier to remove, clean and replace the inner cannula, keeping the outer cannula in place for breathing.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 45-year-old male was brought to the emergency department complaining of loss of consciousness. On examination, he was pale and cyanosed, with a pulse of 60 bpm and blood pressure of 90/50mmhg. To prevent aspiration of secretions, cuffed tracheostomy tube was inserted. When should the cuff be deflated?", "options": [{"label": "A", "text": "Every 2 hours for 5 minutes", "correct": true}, {"label": "B", "text": "Every 1 hour for 5 minutes", "correct": false}, {"label": "C", "text": "Every 3 hours for 10 minutes", "correct": false}, {"label": "D", "text": "Every 2 hours for 10 minutes", "correct": false}], "correct_answer": "A. Every 2 hours for 5 minutes", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Every 2 hours for 5 minutes The cuff should be deflated every 2 hours for 5 minutes to prevent ischemia and damage to the trachea and cartilage necrosis.</p>\n<p><strong>Highyeild:</strong></p><p>CUFFED TRACHEOSTOMY TUBE When the cuff is inflated, it prevents aspiration of pharyngeal secretions into the trachea. It can also prevent air leaks. It is used when there is a danger of aspiration of pharyngeal secretions, as in an unconscious patient or when the patient is put on a respirator. The cuff should be deflated every 2 hours for 5 minutes to prevent ischemia and damage to the trachea and cartilage necrosis. Nowadays, tubes with two cuffs are available, and inflation of the cuff can be alternated to avoid cuff pressure at one site in the trachea The uncuffed tube is less preferred because of the risk of aspiration.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 50-year-old male presented with a cut injury on his right calf while ploughing and developed tetanus. On 3rd day he had stiffness in his neck muscles, and a tracheostomy was done. Which of these materials are used for making tracheostomy tubes?", "options": [{"label": "A", "text": "Silver", "correct": false}, {"label": "B", "text": "PVC", "correct": false}, {"label": "C", "text": "Siliconised PVC", "correct": false}, {"label": "D", "text": "All of the above", "correct": true}], "correct_answer": "D. All of the above", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of the above Tracheostomy tubes are made of silicone, silver, PVC, silastic, and armoured tubes.</p>\n<p><strong>Highyeild:</strong></p><p>Classification of tubes according to the material they are made of: Silver : an alloy of silver, copper and phosphorus, e.g. Fuller, nepas or Jackson's tube Pvc (polyvinyl chloride): they are disposable, single-use tubes and thermolabile, and thus adjust to the tracheal lumen. Silicone: bacteria and secretions do not adhere to the tube, and there is a minimum of crusting. Siliconized pvc: it has the properties of both PVC and silicon, i.e., it is thermolabile and adjusts to the tracheal wall, while silicon prevents crusting. Silastic: it is soft and nonirritating and minimises crusting. Armoured tubes are plastic tubes reinforced by a spiral or rings of stainless steel. They are not easily linked. They are helpful for the proper fitting of tube and proper support.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 33-year-old female complained of chronic cough with purulent sputum, shortness of breath and hemoptysis. On examination, digital clubbing is present. HRCT results show a train track sign indicating bronchiectasis. A bronchoscopy and tracheostomy were done. When should the tracheostomy tube be inserted?", "options": [{"label": "A", "text": "After incision of midline tissues", "correct": false}, {"label": "B", "text": "After separation of strap muscles", "correct": false}, {"label": "C", "text": "After incision of tracheal rings", "correct": true}, {"label": "D", "text": "After injection of lignocaine", "correct": false}], "correct_answer": "C. After incision of tracheal rings", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>After incision of tracheal rings Trachea is fixed with a hook and opened with a vertical incision in the region of the third, fourth, or third, and second rings. This is then converted into a circular opening. The first tracheal ring is never divided as perichondritis of cricoid cartilage with stenosis can result. So, a tracheostomy tube is inserted after incision of subsequent tracheal rings . A tracheostomy tube of appropriate size is inserted and secured by tapes.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. as the first step in tracheostomy, a vertical incision is made in the midline of the neck, extending from the cricoid cartilage to just above the sternal notch. This is the most favoured incision and can be used in emergency and elective procedures. Option: B. After the incision of midline tissues, strap muscles are separated in the midline and retracted laterally. The thyroid isthmus is displaced upwards or divided between the clamps and suture ligated. Option: D. after displacing the thyroid isthmus upwards, a few drops of 4% lignocaine are injected into the trachea to suppress cough when the trachea is incised. Then, the trachea is fixed with a hook and opened with a vertical incision in the region of the third and fourth or third and second rings.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "An 11-year-old girl presented with focal seizures and progressed to status epilepticus. She was put on a mechanical ventilator because of hypoxic arrest. As she required prolonged ventilatory support, tracheostomy and gradual weaning from ventilator support to a t-piece were done. Following stable hemodynamics, a decannulation trial was attempted which failed. What might be the reason for the failure of decannulation?", "options": [{"label": "A", "text": "Granulation", "correct": false}, {"label": "B", "text": "Tracheomalacia", "correct": false}, {"label": "C", "text": "Both AB", "correct": true}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "C. Both AB", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Both A&B The tracheostomy tube should not be kept longer than necessary. Prolonged use of tubes leads to tracheobronchial infections, tracheal ulceration, granulations, stenosis and unsightly scars. All of these can be reasons for the failure of decannulation.</p>\n<p><strong>Highyeild:</strong></p><p>Decannulation Of Tracheostomy Tube To decannulate a patient, the tracheostomy tube is plugged in, and the patient is closely observed. If the patient can tolerate it for 24 hr, the tube can be safely removed. In children, the above procedure is done using a smaller tube. After tube removal, the wound is taped, and the patient is again closely observed. After decannulation, watch the child for several hours for respiratory distress and tachycardia, and colour oximetry is very useful for monitoring oxygen saturation. It may require blood gas determinations. When attempts at decannulation are not successful, look for the cause. It may be Persistence of the condition for which tracheostomy was done. Obstructing granulations around or below the stoma where the tip of the tracheostomy tube had been impinging. Tracheal oedema or subglottic stenosis. Incurving of the tracheal wall at the site of tracheos tome. Tracheomalacia Psychological dependence on tracheostomy and inability to tolerate the resistance of the upper airways.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. obstructing granulations around the stoma or below it where the tip of the tracheostomy tube had been impinging. Option: B. Tracheomalacia in a newborn occurs when the cartilage in the windpipe, or trachea, has not developed properly. Instead of being rigid, the walls of the trachea are floppy, resulting in breathing difficulties soon after birth.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 15-year-old male complained of unilateral progressively increasing throat pain referred to the right ear. Sometimes it becomes so severe that he cannot swallow his saliva. Now he experiences shortness of breath. On local examination, there is trismus of varying degrees. The tonsil is found pushed downward and medially; it blanches on applying slight pressure. There was cervical lymphadenopathy present. It is diagnosed as a peritonsillar abscess extending to the parapharyngeal and prevertebral space causing respiratory distress. Jackson's tracheostomy was inserted. What does the obturator in that tube use for?", "options": [{"label": "A", "text": "For fixation of tube", "correct": false}, {"label": "B", "text": "For introduction to trachea", "correct": true}, {"label": "C", "text": "For piercing the tissues", "correct": false}, {"label": "D", "text": "For Suction", "correct": false}], "correct_answer": "B. For introduction to trachea", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>For introduction to trachea The obturator guides the proper introduction of the tracheostomy tube into the trachea.</p>\n<p><strong>Highyeild:</strong></p><p>Jackson's tracheostomy tube Jackson's tracheostomy tube has three parts outer tube, an inner tube and an obturator. The outer tube is not split, and the inner tube can be fixed to the shield of the outer tube by a lock. The obturator helps in the introduction of the tube into the trachea. The obturator guides the proper introduction of the tracheostomy tube into the trachea. It is a metal tracheostomy tube and is cost-effective and has less infection risk as the surface of a metal is less porous than a plastic tracheostome and less likely to grow germs.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 53-year-old male presented to the emergency room after a motor vehicle accident. He was a restrained driver going approximately 40 mph. The patient was hemodynamically stable when he presented to the emergency, complaining of chest pain. The patient had ct of the neck that showed an acute fracture of the mid anterior thyroid cartilage and acute comminated fracture of both aspects of the cricoid cartilage. A tracheostomy was done at 3rd tracheal ring. Why did the first tracheal ring not divided into usual tracheostomy procedures?", "options": [{"label": "A", "text": "Presence of large vessels", "correct": false}, {"label": "B", "text": "Cause stenosis of subglottis", "correct": true}, {"label": "C", "text": "Prevent damage to nerves", "correct": false}, {"label": "D", "text": "Avoid injury to thyroid isthmus", "correct": false}], "correct_answer": "B. Cause stenosis of subglottis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cause stenosis of subglottis The first tracheal ring is never divided as perichondritis of cricoid cartilage with subglottic stenosis and is always avoided. Also, the larynx is narrower near the first tracheal ring – hence, division of the first tracheal ring can result in significant stenosis of the larynx.</p>\n<p><strong>Highyeild:</strong></p><p>HIGH TRACHEOSTOMY A high tracheostomy is done above the level of thyroid isthmus (isthmus lies against II, III and IV tracheal rings). It violates the first ring of the trachea. Tracheostomy at this site can cause perichondritis of the cricoid cartilage and subglottic stenosis and is always avoided. The only indication for high tracheostomy is carcinoma of the larynx because, in such cases, the entire larynx anyway would ultimately be removed and a fresh tracheostome made in a clean area lower down. Apart from ca larynx, in all other conditions, we usually prefer low tracheostomy.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. trachea is deep and close to several large vessels below the isthmus level, not at the first tracheal ring. Anteriorly, it is covered from backwards by the manubrium sterni, the thymus remains, the left brachiocephalic vein, the aortic arch, the brachiocephalic trunk, and the left common carotid arteries, and the deep cardiac plexus. Option: C. the nerves are not injured at the first tracheal ring. The two nerves of importance that pass through the thyroid are the left and right recurrent laryngeal nerves. They are often located on the lateral aspect of the thyroid gland near the vicinity of the inferior thyroid artery. Option: D. thyroid isthmus is located over 2-3rd tracheal rings. And high tracheostomy is done above this level.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 52-year-old chronic smoker presented with hoarseness, throat pain and, dysphagia, weight loss. On examination, upper and middle jugular nodes are palpable. Laryngoscopy and biopsy results show a carcinoma larynx. A total laryngectomy was done, and a permanent tracheostome was placed. Which was the tracheostomy done in this case?", "options": [{"label": "A", "text": "High Tracheostomy", "correct": true}, {"label": "B", "text": "Mid Tracheostomy", "correct": false}, {"label": "C", "text": "Low Tracheostomy", "correct": false}, {"label": "D", "text": "Mini Tracheostomy", "correct": false}], "correct_answer": "A. High Tracheostomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>High Tracheostomy The only indication for high tracheostomy is carcinoma of the larynx because, in such cases, the entire larynx would ultimately be removed and a fresh tracheostome made in a clean area lower down.</p>\n<p><strong>Highyeild:</strong></p><p>HIGH TRACHEOSTOMY A high tracheostomy is done above the level of thyroid isthmus (isthmus lies against II, III and IV tracheal rings). It violates the first ring of the trachea. Tracheostomy at this site can cause perichondritis of the cricoid cartilage and subglottic stenosis and is always avoided. The only indication for high tracheostomy is carcinoma of the larynx because, in such cases, the entire larynx would ultimately be removed and a fresh tracheostome made in a clean area lower down. Apart from ca larynx, in all other conditions, we usually prefer low tracheostomy.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. a mid tracheostomy is the preferred one and is done through the ii or iii rings would ENT ail division of the thyroid isthmus or its retraction upwards or downwards to expose this part of trachea. Option: C. A low tracheostomy is done below the level of isthmus. Trachea is deep at this level and close to several large vessels; also, there are difficulties with the tracheostomy tube, which impinges on the suprasternal notch. Option: D. Mini tracheostomy is a procedure for opening the airway through the cricothyroid membrane. The patient's head and neck are extended, lower border of the thyroid cartilage and cricoid ring are identified. It is an emergency procedure to buy time to allow the patient to be carried to the operation theatre.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 19 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 13</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Tracheostomy - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 13</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 13 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "All are true about croup except:", "options": [{"label": "A", "text": "Age group 3-6 yrs", "correct": false}, {"label": "B", "text": "Viral Condition", "correct": false}, {"label": "C", "text": "Presence of cough with low-grade fever", "correct": false}, {"label": "D", "text": "Tracheostomy is the mainstay of treatment", "correct": true}], "correct_answer": "D. Tracheostomy is the mainstay of treatment", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tracheostomy is the mainstay of treatment Treatment of croup is Broad-spectrum penicillin (for secondary bacterial infection) IV steroids, if the child is in distress Humidified air IV fluids Nebulisation with adrenaline. Despite the above measures, if respiratory obstruction increases, then only intubation/tracheostomy is done.</p>\n<p><strong>Highyeild:</strong></p><p>ACUTE LARYNGOTRACHEOBRONCHITIS/CROUP It is a viral infection caused by parainfluenza type 1, 2, and sometimes 3. The critical area involved is the subglottic larynx producing oedema with stridor and respiratory distress. X-ray (PA view) larynx shows a typical “steeple sign”, but X-rays are avoided as any manipulation may precipitate acute obstruction.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option:A. Croup is shared between the age group of 3-6 yrs is a true statement. Option: B. It is a viral infection caused by parainfluenza type 1, 2, and sometimes 3 Option: C. The presence of cough with low-grade fever is also a true statement.</p>\n<p><strong>Extraedge:</strong></p><p>Points to Remember -Indications for Intubation Rising CO2 level Worsening neurologic status Decreasing respiratory rate</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are true about Laryngomalacia except:", "options": [{"label": "A", "text": "Elective tracheostomy must be done as soon as the condition is diagnosed", "correct": true}, {"label": "B", "text": "Pt condition improves by placing in the prone position", "correct": false}, {"label": "C", "text": "Cry of the child is normal", "correct": false}, {"label": "D", "text": "Usually disappears by the age of 2", "correct": false}], "correct_answer": "A. Elective tracheostomy must be done as soon as the condition is diagnosed", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Elective tracheostomy must be done as soon as the condition is diagnosed Elective tracheostomy should be done after it is diagnosed. It is only done when there is more difficulty in breathing.</p>\n<p><strong>Highyeild:</strong></p><p>LARYNGOMALACIA It is the most common congenital abnormality of the larynx. It is characterised by excessive flaccidity of the supraglottic larynx, which is sucked in during inspiration producing stridor and sometimes cyanosis. Stridor is increased on crying but subsides on placing the child in a prone position; crying is typical. The condition manifests at birth or soon after and usually disappears by two years of age. Direct laryngoscopy shows elongated epiglottis, curled upon itself (omega-shaped ), and floppy aryepiglottic folds. Treatment is conservative. A tracheostomy may be required for some cases of severe respiratory obstruction.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Pt condition improves by placing in the prone position is a true statement. Option: C. The child's cry is also expected to be a true statement. Option: D. Usually disappearing by age 2 is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Tracheostomy is done at which level:", "options": [{"label": "A", "text": "T1", "correct": false}, {"label": "B", "text": "T2", "correct": false}, {"label": "C", "text": "T3", "correct": true}, {"label": "D", "text": "T4", "correct": false}], "correct_answer": "C. T3", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>T3 Tracheostomy is done at the level of T3 mainly. In tracheostomy, need to cross a vertical incision is made on the second and the third or third and fourth tracheal ring after retracting the thyroid isthmus upwards</p>\n<p><strong>Highyeild:</strong></p><p>TRACHEOSTOMY T1 is a high tracheostomy incision made on the first tracheal ring T5 and below is known as a low tracheostomy incision below the thyroid isthmus. Is a procedure of making an opening in the anterior wall of the trachea Indications Bypass obstruction Bypass Laryngeal carcinoma To prevent aspiration Decrease the dead space {reduce by 50%} Breathing becomes comfortable and suitable for restrictive lung d/s Recurrent suction Before other surgical procedures like laryngotomy</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You are a senior resident doctor in the Department of ENT. A 3-year-old boy, while having dinner, suddenly becomes aphonic and develops respiratory distress. What should be the appropriate initial management:", "options": [{"label": "A", "text": "Cricothyrotomy", "correct": false}, {"label": "B", "text": "Emergency Tracheostomy", "correct": false}, {"label": "C", "text": "Finger Sweep Manoeuvre", "correct": false}, {"label": "D", "text": "Heimlich Manoeuvre", "correct": true}], "correct_answer": "D. Heimlich Manoeuvre", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Heimlich Manoeuvre This child will likely have an accidental inhalation of food in the airway . The initial immediate management is the Heimlich manoeuvre.</p>\n<p><strong>Highyeild:</strong></p><p>HEIMLICH MANOEUVRE The initial immediate management is the Heimlich manoeuvre, where multiple thrusts are given with the wrist in the upper epigastrium while standing behind the patient. This forcefully pushes air out of the lungs moving the foreign body along. The Heimlich manoeuvre can lead to injury to the abdominal organs and therefore is done only when there is aphonia and severe respiratory distress. Heimlich manoeuvre. Sudden thrust directed upwards and backwards, below the epigastrium, squeezes the air from the lungs, sufficient to dislodge a foreign body.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Cricothyrotomy is not the appropriate initial management of foreign bodies in the airway. Option: B. Emergency Tracheostomy also is not the appropriate initial management of foreign bodies in the airway. Option: C. Finger Sweep Manoeuvre also is not the appropriate initial management of foreign bodies in the airway.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 44-year-old man who met with a motor vehicle catastrophe came to the casualty with severe maxillofacial trauma. His pulse rate was 110/min, BP was 110/70 mmHg, and Sp0-78% with oxygen. A tracheostomy is done in this patient as an initial treatment. In emergency tracheostomy, the following structures are damaged except", "options": [{"label": "A", "text": "Isthmus of the thyroid", "correct": false}, {"label": "B", "text": "Inferior Thyroid Artery", "correct": true}, {"label": "C", "text": "Inferior Thyroid Vein", "correct": false}, {"label": "D", "text": "Thyroidea Ima Artery", "correct": false}], "correct_answer": "B. Inferior Thyroid Artery", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Inferior Thyroid Artery The inferior thyroid artery does not get injured as it goes on the under surface of the thyroid. Rest all vessels injury can occur as a complication after tracheostomy.</p>\n<p><strong>Highyeild:</strong></p><p>Indications of tracheostomy Bypass obstruction. Bypass Laryngeal carcinoma. To prevent aspiration. Decrease the dead space {reduce by 50%}. Before other surgical procedures like Laryngotomy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Your ENT professor did a tracheostomy on a patient with respiratory obstruction due to maxillofacial trauma following a road traffic accident. Which of the following statements is false about tracheostomy?", "options": [{"label": "A", "text": "Decrease dead space by 30-50%", "correct": false}, {"label": "B", "text": "A frequent problem in infants after tracheostomy is difficult decannulation", "correct": false}, {"label": "C", "text": "In emergency tracheostomy transverse incision is given two fingers above the sternal notch", "correct": true}, {"label": "D", "text": "A complication of paediatrics tracheostomy is pneumothorax", "correct": false}], "correct_answer": "C. In emergency tracheostomy transverse incision is given two fingers above the sternal notch", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>In emergency tracheostomy transverse incision is given two fingers above the sternal notch In an emergency tracheostomy, a vertical incision is given . A transverse incision is given in elective tracheostomy.</p>\n<p><strong>Highyeild:</strong></p><p>Indications of tracheostomy Bypass obstruction. Bypass Laryngeal carcinoma. To prevent aspiration. Decrease the dead space {reduce by 50%}. Before other surgical procedures like Laryngotomy.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Tracheostomy Decrease dead space by 30-50% is a true statement. Option: B. A frequent problem in infants after tracheostomy is difficult decannulation is also a true statement. Option: D. A complication of pediatric tracheostomy is pneumothorax is also a true statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Kamla, 4 yrs of age, presented in emergency with mild respiratory distress. On a laryngoscopy, she was diagnosed with multiple juvenile larynx papillomatoses. The next line of management is:", "options": [{"label": "A", "text": "Tracheostomy", "correct": false}, {"label": "B", "text": "Micro Laryngoscopy", "correct": true}, {"label": "C", "text": "Steroid", "correct": false}, {"label": "D", "text": "Antibiotics", "correct": false}], "correct_answer": "B. Micro Laryngoscopy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Micro Laryngoscopy The patient (a four-year-old girl) is presenting with mild respiratory distress due to multiple Juvenile papillomatosis of the The management in such a case is micro laryngoscopic surgery using a CO2 laser to ablate the lesion.</p>\n<p><strong>Highyeild:</strong></p><p>Juvenile papillomatosis of the larynx Juvenile papillomatosis is the most common benign neoplasm of the larynx in children. It is viral in origin and is caused by human papilloma DNA virus types 6 and 11. It is presumed that affected children got the disease at birth from their mothers with vaginal human papillomavirus disease. The patient, often a child between 3 and 5 years old, presents with hoarseness, aphonia, respiratory difficulty, or even stridor. Diagnosis is made by flexible fibreoptic laryngoscopy and later confirmed by direct laryngoscopy and biopsy. Papillomas are known for recurrence but rarely undergo malignant change. Treatment consists of micro laryngoscopy and CO2 laser excision, avoiding injury to a vocal ligament. Supraglottic papillomatosis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Tracheostomy is reserved for patients with severe respiratory distress. Option: C. Steroids have no role. Option: D. Antibiotics also have no role.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Contraindications for percutaneous dilatational tracheostomy are all the following except:", "options": [{"label": "A", "text": "Obese Patient", "correct": false}, {"label": "B", "text": "Uncorrectable Coagulopathies", "correct": false}, {"label": "C", "text": "COMA", "correct": true}, {"label": "D", "text": "Difficult to palpate larynx and trachea", "correct": false}], "correct_answer": "C. COMA", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>COMA Percutaneous dilatational tracheostomy is done in ICU, where the patient is already intubated and monitored. The procedure is avoided in patients who are obese, have a neck mass difficult to intubate, are challenging to extend the neck, larynx and trachea are not easily palpable, and have uncorrected coagulopathies.</p>\n<p><strong>Highyeild:</strong></p><p>Percutaneous dilatational tracheostomy This type of tracheostomy is done in ICU, where the patient is already intubated and monitored. It is done under sedation. Advantages of the procedure include: (i) No need to transport the patient to the operation theatre. (ii) avoiding operation theatre (OT) expenses. (iii) Avoid ICU nosocomial infections to be carried to OT and earlier patient discharge. The procedure is avoided in patients who are obese, have a neck mass, difficult to intubate, difficult to extend the neck and larynx. Trachea is not easily palpable or has uncorrectable coagulopathies.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. percutaneous dilatational tracheostomy is avoided in obese patients. Option: B. percutaneous dilatational tracheostomy is avoided in Uncorrectable Coagulopathies. Option: D. percutaneous dilatational tracheostomy is avoided in patients with difficulty palpating the larynx and trachea.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following muscles is not a depressor of the larynx?", "options": [{"label": "A", "text": "Thyrohyoid", "correct": true}, {"label": "B", "text": "Sternohyoid", "correct": false}, {"label": "C", "text": "Sternothyroid", "correct": false}, {"label": "D", "text": "Omohyoid", "correct": false}], "correct_answer": "A. Thyrohyoid", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Thyrohyoid The Thyrohyoid is a primary elevator of the larynx .</p>\n<p><strong>Highyeild:</strong></p><p>Extrinsic muscles of the larynx They connect the larynx to the neighbouring structures and are divided into elevators or depressors of the larynx. (a) Elevators. Primary elevators act directly as they are attached to the thyroid cartilage, including the stylopharyngeus, salpingopharyngeus, palatopharyngeus, and thyrohyoid. Secondary elevators act indirectly as they are attached to the hyoid bone, including mylohyoid (primary), digastric, stylohyoid and geniohyoid. (b) Depressors. They include sternohyoid, sternothyroid, and omohyoid.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Sternohyoid is a depressor of the larynx. Option: C. sternothyroid is also a depressor of the larynx Option: D. omohyoid is also a depressor of the larynx.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 25-year-old school teacher presented to the ENT department with complaints of voice fatigue, difficulty in singing high notes and increased breathiness along with roughness and harshness of voice and pain on prolonged phonation. Which of the following is most likely the diagnosis?", "options": [{"label": "A", "text": "Vocal Polyp", "correct": false}, {"label": "B", "text": "Vocal Nodule", "correct": true}, {"label": "C", "text": "Reinke’s Edema", "correct": false}, {"label": "D", "text": "Contact Ulcer", "correct": false}], "correct_answer": "B. Vocal Nodule", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Vocal Nodule Trauma to the vocal cord in the form of vocal abuse or misuse causes oedema and haemorrhage in the submucosal space. This undergoes hyalinisation and fibrosis . The overlying epithelium also undergoes hyperplasia forming a nodule. Patients with vocal nodules complain of hoarseness . Vocal fatigue and pain in the neck on prolonged phonation are other common symptoms.</p>\n<p><strong>Highyeild:</strong></p><p>VOCAL NODULE They appear symmetrically on the free edge of the vocal cord, at the junction of the anterior one-third, with the posterior two-thirds, as this is the area of maximum vibration of the cord and thus subject to maximum trauma. Their size varies from that of a pin-head to half a pea. They result from vocal trauma when a person speaks in unnatural low tones for prolonged periods or at high intensities. They mostly affect teachers, actors, vendors or pop singers. They are also seen in school-going children who are too assertive and talkative. Pathologically, trauma to the vocal cord in the form of vocal abuse or misuse causes oedema and haemorrhage in the submucosal space. This undergoes hyalinisation and fibrosis. The overlying epithelium also undergoes hyperplasia forming a nodule. Patients with vocal nodules complain of hoarseness. Vocal fatigue and pain in the neck on prolonged phonation are other common symptoms. Vocal nodules.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Vocal polyp is the result of vocal abuse or misuse. Other contributing factors are allergies and smoking. Primarily, it affects men in the age group of 30–50 years. Typically, a vocal polyp is unilateral, arising from the same position as a vocal nodule Option: C. Reinke’s edema is bilateral symmetrical swelling of the whole of the membranous part of the vocal cords, most often seen in middle-aged men and women. This is due to edema of the subepithelial space (Reinke’s space) of the vocal lines. Option: D. Contact ulcer is due to faulty voice production in which vo- cal processes of arytenoids hammer against each other, resulting in ulceration and granuloma formation. Some cases are due to gastric reflux. Chief complaints are a hoarse voice, a constant desire to clear the throat, and pain in the throat which is worse on phonation.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All of the following are true about tracheostomy tubes except:", "options": [{"label": "A", "text": "Consist of metallic tubes", "correct": false}, {"label": "B", "text": "Cuffed tube used for IPPV", "correct": false}, {"label": "C", "text": "Portex tube is ideally changed every 15 days", "correct": true}, {"label": "D", "text": "Consists of silicon and polyvinyl chloride material", "correct": false}], "correct_answer": "C. Portex tube is ideally changed every 15 days", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Portex tube is ideally changed every 15 days The tracheostomy tube is changed every two to three days to prevent obstruction from crusting.</p>\n<p><strong>Highyeild:</strong></p><p>TRACHEOSTOMY TUBE</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. metallic tracheostomy tubes are available. Option: B. The cuffed tube used for ippv is a true statement. Option: D. The tracheostomy tube consists of silicon and polyvinyl chloride material.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "What is the full form of IPPV?", "options": [{"label": "A", "text": "Intermittent pressure per vaginally", "correct": false}, {"label": "B", "text": "Intermittent positive pressure ventilation", "correct": true}, {"label": "C", "text": "Immediate positive pressure ventilation", "correct": false}, {"label": "D", "text": "Immediate pressure with pressurized ventilator", "correct": false}], "correct_answer": "B. Intermittent positive pressure ventilation", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Intermittent positive pressure ventilation Full form of IPPV is intermittent positive pressure ventilation.</p>\n<p><strong>Highyeild:</strong></p><p>IPPV IPPV is intermittent positive pressure ventilation. It is of use in patients who require intermittent positive pressure ventilation. A cuffed tracheostomy tube has to be used to prevent air leak and hence maintain positive pressure. A cuffed endotracheal tube is not to be put in for more than three days since it is more likely to cause ulceration granulation and subglottic and laryngeal stenosis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 59-year-old male patient comes to ENT opd. He was diagnosed to have carcinoma larynx. Maintenance of the airway during laryngectomy in a patient with carcinoma of the larynx is best done by:", "options": [{"label": "A", "text": "Laryngeal tube", "correct": false}, {"label": "B", "text": "Combi Tube", "correct": false}, {"label": "C", "text": "Laryngeal mask airway", "correct": false}, {"label": "D", "text": "Tracheostomy", "correct": true}], "correct_answer": "D. Tracheostomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tracheostomy During laryngectomy the intubation tube is kept away from the field of surgery ( i.e. the larynx ) by doing a tracheostomy in patients not previously tracheotomised. Here intubation is done through the tracheostomy.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Laryngeal tube deliver oxygen/anaesthesia at or above the laryearnd therefore all these will not be of utility in a patient with a mass obstructing the cavity of the larynx. It will also interfere with the field of surgery. Option: B. Combi tube also deliver oxygen/anaesthesia at or above the larynx and therefore all these will not be of utility in a patient with a mass obstructing the cavity of the larynx. It will also interfere with the field of surgery. Option: C. Laryngeal mask airway also deliver oxygen/anaesthesia at or above the larynx and therefore all these will not be of utility in a patient with a mass obstructing the cavity of the larynx. It will also interfere with the field of surgery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 23 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 5</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Treatment of Otosclerosis & Stapedectomy - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 5</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 5 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "You have successfully observed your senior perform a procedure on a patient with otosclerosis, and you’re required to write the procedure steps in the post-operative notes. Arrange the steps in the correct sequence below: 1. Meatal incision and elevation of the tympanometry flap. 2. Creation of a hole in the stapes footplate (stapedotomy) or removal of a part of the footplate (stapedectomy). 3. Repositioning the tympanometry flap 4. Exposure of stapes area 5. Removal of stapes superstructure. 6. Placement of prosthesis. Select the correct answer from the given below:", "options": [{"label": "A", "text": "1, 3, 5, 2, 4, 6", "correct": false}, {"label": "B", "text": "1, 4, 5, 2, 6, 3", "correct": true}, {"label": "C", "text": "3, 2, 1, 4, 5, 6", "correct": false}, {"label": "D", "text": "2, 4, 6, 1, 3, 5", "correct": false}], "correct_answer": "B. 1, 4, 5, 2, 6, 3", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>1, 4, 5, 2, 6, 3 1,4,5,2,3,6 is the correct sequence of the steps of stapedectomy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "You have multiple patients in the ward with otosclerosis who want to undergo corrective surgery. Which of the following is not a selection criterion for stapes surgery?", "options": [{"label": "A", "text": "The average air-bone gap should be at least 15 dB", "correct": false}, {"label": "B", "text": "The hearing threshold for air conduction should be 30 dB or worse.", "correct": false}, {"label": "C", "text": "Rinne negative for 256 and 512 Hz.", "correct": false}, {"label": "D", "text": "The speech discrimination score should be 20% or more.", "correct": true}], "correct_answer": "D. The speech discrimination score should be 20% or more.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>The speech discrimination score should be 20% or more. The speech discrimination score should be 60 % or more, not 20%.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A - Average air-bone gap should be at least 15 dB. Option: B : The hearing threshold for air conduction should be 30 dB or worse. (At this level, a patient starts feeling socially handicapped.) Option: C - Rinne negative for 256 and 512 Hz to select patients for stapes surgery.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are factual statements regarding the use of sodium fluoride in the treatment of otosclerosis except:", "options": [{"label": "A", "text": "It inhibits osteoblastic activity.", "correct": true}, {"label": "B", "text": "Used in the active phase of otosclerosis when Schwartz sign is positive", "correct": false}, {"label": "C", "text": "Has proteolytic activity (bone enzymes)", "correct": false}, {"label": "D", "text": "Contraindicated in chronic nephritis", "correct": false}], "correct_answer": "A. It inhibits osteoblastic activity.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>It inhibits osteoblastic activity. Sodium fluoride reduces osteoclastic bone resorption and increases osteoblastic bone formation, which promotes recalcification and reduces bone remodeling in actively expanding osteolytic lesions.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: B- Sodium fluoride is used in the active phase of otosclerosis when the Schwartz sign is positive. Option: C- Sodium fluoride also inhibits proteolytic enzymes that are cytotoxic to the cochlea and lead to SNHL (Hence especially useful in cochlear otosclerosis). Option: D- Sodium fluoride is contraindicated in chronic nephritis patients.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A middle-aged patient admitted to the ENT ward is to undergo a stapedectomy for his Otosclerosis. As an intern on duty, you are asked to innumerate all the possible complications to the patient before the procedure. Which of the following is not a complication of the process?", "options": [{"label": "A", "text": "Sensorineural Hearing Loss", "correct": false}, {"label": "B", "text": "Conductive Hearing Loss", "correct": false}, {"label": "C", "text": "Vertigo", "correct": false}, {"label": "D", "text": "Malleus Dislocation", "correct": true}], "correct_answer": "D. Malleus Dislocation", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Malleus Dislocation Incus dislocation is a post-op complication of stapedectomy, not malleus.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- Sensorineural Hearing Loss Can occur due to intraoperative trauma/labyrinthitis. Option: B- Conductive Hearing Loss may occur due to short/loose/ dislodgement of the prosthesis. Option: C- Vertigo may occur Early in the postoperative period (intraoperative trauma, serous labyrinthitis, long prosthesis), Late due to perilymph fistula and benign paroxysmal positional vertigo.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In the given diagram Least Common Post - Operative Complication Of the Following Procedure Is:", "options": [{"label": "A", "text": "Dislodged of Prosthesis", "correct": false}, {"label": "B", "text": "Necrosis of Incus", "correct": false}, {"label": "C", "text": "Perilymph Fistula", "correct": false}, {"label": "D", "text": "Abducens Nerve Palsy", "correct": true}], "correct_answer": "D. Abducens Nerve Palsy", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685004638190-QTDE063006IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Abducens Nerve Palsy The above diagram is of a Stapedectomy with the placement of the prosthesis . Abducens nerve palsy is the least common postoperative complication of stapedectomy. Facial nerve palsy can occur as a complication of stapedectomy.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect Option:s:- Option: A- Dislodged Prosthesis may be a post-op complication of stapedectomy. Option: B- Necrosis of the Incus may be a post-op complication of stapedectomy. Option: C- Perilymph Fistula may be one of the post-op complications of stapedectomy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 15 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 6</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Tumours of Nose & Sinuses - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 6</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 6 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Woodworkers are associated with sinus Carcinoma:", "options": [{"label": "A", "text": "Adeno Carcinoma", "correct": true}, {"label": "B", "text": "Squamous cell Carcinoma", "correct": false}, {"label": "C", "text": "Anaplastic Carcinoma", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "A. Adeno Carcinoma", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Adeno Carcinoma Workers in the furniture industry develop adenocarcinoma of the Ethmoids and upper nasal cavities.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Those engaged in Nickel refining get squamous cell carcinoma. Option: C. Those engaged in Nickel refining may also get anaplastic carcinoma.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Most common presentation in nasopharyngeal carcinoma is -", "options": [{"label": "A", "text": "U/L serous otitis media", "correct": false}, {"label": "B", "text": "Cervical Lymphadenopathy", "correct": true}, {"label": "C", "text": "Rhinolalia Clausa", "correct": false}, {"label": "D", "text": "Eustachian Tube Block", "correct": false}], "correct_answer": "B. Cervical Lymphadenopathy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cervical Lymphadenopathy In nasopharyngeal carcinoma , the most common presentation is cervical lymphadenopathy .</p>\n<p><strong>Highyeild:</strong></p><p>NASOPHARYNGEAL CARCINOMA MC site : Fossa of Rosenmuller Presentation Hearing loss, Glue Nasal Blockade, Nasal discharge, Pain, Bleeding from the nose, Anosmia. Trotter’s triad. Soft palate palsy Hearing loss Facial pain MC Presentation : Cervical lymphadenopathy</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are true about Ringertz tumour except:", "options": [{"label": "A", "text": "Caused By Hpv6,11,18,33", "correct": true}, {"label": "B", "text": "10-15 per cent incidence of malignancy", "correct": false}, {"label": "C", "text": "Treatment is medial maxillectomy", "correct": false}, {"label": "D", "text": "Most common site of origin is a lateral wall of the nose in the middle meatus.", "correct": false}], "correct_answer": "A. Caused By Hpv6,11,18,33", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Caused By Hpv6,11,18,33 The cause of ringertz tumour is HPV 6, 11, 16,18 and not 33.</p>\n<p><strong>Highyeild:</strong></p><p>I NVERTED P APILLOMA (T RANSITIONAL C ELL P APILLOMA OR R INGERTZ T UMOUR OR S C It is a tumour of the non-olfactory mucosa of the nose (Schneiderian membrane) and paranasal sinuses. The most common site of origin is a lateral wall of the nose in the middle meatus. It is so named because hyperplastic papillomatous tissue grows into the stroma rather than in an exophytic manner. Human papillomavirus is thought to be responsible for its aetiology. Clinically, men are affected more than women in the age group of 40–70. (A) Inverted papilloma in a 79-year-old male (right side). (B) CT scan of the same.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. In 10–15% of cases, it is associated with malignancy is a true statement. Option: C. Medial maxillectomy is the treatment of choice is a true statement. Option: D. Most common site of origin is a lateral wall of the nose in the middle meatus is a true statement.</p>\n<p><strong>Extraedge:</strong></p><p>It is almost always unilateral and presents with nasal obstruction, nasal discharge and epistaxis. Treatment - Medial maxillectomy is the treatment of choice. It can be performed by lateral rhinotomy or sublabial degloving approach.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Identify the image given below.", "options": [{"label": "A", "text": "Walsham Forceps", "correct": false}, {"label": "B", "text": "Asch septum Forceps", "correct": true}, {"label": "C", "text": "Luc’S Forceps", "correct": false}, {"label": "D", "text": "Tilley’s dressing forceps", "correct": false}], "correct_answer": "B. Asch septum Forceps", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003658592-QTDE082006IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Asch septum Forceps The image given above is of Asch forceps.</p>\n<p><strong>Highyeild:</strong></p><p>ASCH SEPTUM FORCEPS It is used for reducing fractures of the nasal septum.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Walsham Forceps Option: C. Luc’S Forceps Option: D. Tilley’s dressing forceps</p>\n<p><strong>Extraedge:</strong></p><p>Walsham’s forceps- Used for disimpacting and reducing fractures of nasal bone. Luc’s forceps - Used in Caldwell-Luc operation (to remove mucosa), submucosal resection (SMR) operation (to remove bone or cartilage), polypectomy (to grasp and avulse polyps) and to take biopsy from the nose or throat. Tilley’s or Hartman’s forceps is used in packing of ear canal or nasal cavity.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Topical steroids are not recommended in the treatment of-", "options": [{"label": "A", "text": "Allergic Fungal Sinusitis", "correct": false}, {"label": "B", "text": "Antrochoanal Polyp", "correct": true}, {"label": "C", "text": "Ethmoidal Polyps", "correct": false}, {"label": "D", "text": "Chronic Rhinosinusitis", "correct": false}], "correct_answer": "B. Antrochoanal Polyp", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Antrochoanal Polyp Topical steroids are not recommended in the treatment of antrochoanal polyp.</p>\n<p><strong>Highyeild:</strong></p><p>Treatment of the Antrochoanal polyp is easily removed by avulsion either through the nasal or oral route. Recurrence is uncommon after removal. If there is a recurrence, then Caldwell – Luc operation is done. Endoscopic sinus surgery can also be done.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- In all other options Topical steroids may be recommended.</p>\n<p><strong>Extraedge:</strong></p><p>Recurrence is uncommon in the case of an antrochoanal polyp. Antrochoanal polyps arise from the maxillary artrum and grow into the choana and nasal cavities.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 55-year-old female presented to the ENT department with a pearly nodule over the ala of the nose on the right side. He gave the history of its presence for the last six months and said it is slowly growing but mostly confined to this area. Identify the lesion with the Image given below:", "options": [{"label": "A", "text": "Squamous Cell Carcinoma", "correct": false}, {"label": "B", "text": "Melanoma", "correct": false}, {"label": "C", "text": "Basal Cell Carcinoma", "correct": true}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "C. Basal Cell Carcinoma", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003658618-QTDE082010IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Basal Cell Carcinoma The above image is of basal cell carcinoma.</p>\n<p><strong>Highyeild:</strong></p><p>BASAL CELL CARCINOMA This is the most common malignant tumour involving the skin of the nose, equally affecting males and females in the age group of 40–60 years. Common sites on the nose are the tip and the ala. It may present as a cyst, papule-pearly nodule, or an ulcer with rolled edges.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Squamous cell carcinoma is the second most common malignancy, equally affecting both sexes in 40 to 60 It occurs as an infiltrating nodule or an ulcer with rolled-out edges 20% of nodal metastasis is seen. Melanoma is the least common variety and presents as a superficial spreading or nodular invasive type. Option: B. Melanoma is the least common variety. Clinically, it is a superficially spreading type (slow-growing) or nodular invasive type. Treatment is surgical excision.</p>\n<p><strong>Extraedge:</strong></p><p>It is very slow growing and remains confined to the skin for a long time. Underlying cartilage or bone may get invaded.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 16 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 12</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Tuning Fork Tests - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 12</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 12 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Name the Test being performed:", "options": [{"label": "A", "text": "Gelle Test", "correct": false}, {"label": "B", "text": "Bing Test", "correct": true}, {"label": "C", "text": "ABC Test", "correct": false}, {"label": "D", "text": "Weber’s Test", "correct": false}], "correct_answer": "B. Bing Test", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003558151-QTDE045002IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Bing Test The Bing test is being performed in the above image. It is a test of bone conduction and examines the effect of occlusion of the ear canal on the hearing.</p>\n<p><strong>Highyeild:</strong></p><p>Bing Test It is a test of bone conduction and examines the effect of occlusion of the ear canal on the hearing. A vibrating tuning fork is placed on the mastoid while the examiner alternately closes and opens the ear canal by pressing the tragus inwards. A normal person with sensorineural hearing loss hears louder when the ear canal is occluded and softer when the canal is open (Bing positive). A patient with conductive hearing loss will appreciate no change (Bing negative) .</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Gelle’s test is performed by placing a vibrating fork on the mastoid, while Siegel’s speculum changes the air pressure in the ear canal. Gelle’s test is positive in normal persons and those with sensorineural hearing loss. It is negative when the ossicular chain is fixed or disconnected. Option C. In the ABC test, the patient's bone conduction is compared with the examiner's (assuming the examiner has normal hearing). The patient and examiner's external auditory meatus should be occluded (by pressing the tragus inwards) to prevent ambient noise from entering through the AC route. In conductive deafness, the patient and the examiner hear the fork at the same time. In sensorineural deafness, the patient hears the fork for a shorter duration. Option D. In the Weber test, a vibrating tuning fork is placed in the middle of the forehead or the vertex and the patient is asked in which ear the sound is heard. Usually, it is heard equally in both ears. It is lateralised to the worse ear in conductive deafness and to the better ear in sensorineural deafness.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Rinne's test negative bilaterally means?", "options": [{"label": "A", "text": "Bilateral conductive hearing loss", "correct": true}, {"label": "B", "text": "Bilateral sensorineural hearing loss", "correct": false}, {"label": "C", "text": "Both A and B", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "A. Bilateral conductive hearing loss", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Bilateral conductive hearing loss Rinne's positive means AC > BC seen in normal persons and SNHL . A negative Rinne means BC > AC, e. conductive hearing loss. So bilateral Rinne’s negative means bilateral conductive hearing loss.</p>\n<p><strong>Highyeild:</strong></p><p>Rinne’s Test In this test, the air conduction of the ear is compared with its bone conduction. A vibrating tuning fork is placed on the patient’s mastoid, and when he stops hearing, it is brought beside the meatus. If he still hears, AC is more than BC. Alternatively, the patient is asked to compare the loudness of sound heard through air and bone conduction. Rinne's test is positive when AC is longer or louder than BC. It is seen in normal persons or those having sensorineural deafness. A negative Rinne (BC > AC) is seen in conductive deafness.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. Rinne positive is seen in normal individuals or persons with sensorineural hearing loss.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which one of the following tests is used to detect malingering?", "options": [{"label": "A", "text": "Stenger's Test", "correct": true}, {"label": "B", "text": "Bing's Test", "correct": false}, {"label": "C", "text": "Weber's Test", "correct": false}, {"label": "D", "text": "Rinne's Test", "correct": false}], "correct_answer": "A. Stenger's Test", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Stenger's Test Occasionally patients willfully or subconsciously exaggerate their hearing loss. This is functional hearing loss or pseudo-hypacusis, or Stenger test is used to detect malingering.</p>\n<p><strong>Highyeild:</strong></p><p>Stenger Test The principle involved is that if a tone of two intensities, one greater than the other, is delivered to two ears simultaneously. Only the ear which receives a tone of greater intensity will hear it. To do this test, take two tuning forks of equal frequency, strike them, and keep them 25 cm from each ear. The patient will claim to hear it in the normal ear. Now bring the tuning fork on the side of feigned deafness to within 8 cm, keeping the tuning fork on the normal side at the same distance. The patient will deny hearing anything even though the tuning fork on the normal side is where it could have been heard earlier. A person with true deafness should continue to hear on the normal side. Patients should be blindfolded during this test.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption B. Bing test is a test of bone conduction and examines the effect of occlusion of the ear canal on hearing. A vibrating tuning fork is placed on the mastoid while the examiner alternately closes and opens the ear canal by pressing the tragus inwards. A normal person or one with sensorineural hearing loss hears louder when the ear canal is occluded and softer when the canal is open (Bing positive). A patient with conductive hearing loss will appreciate no change (Bing negative). O ption C. In the Weber test, a vibrating tuning fork is placed in the middle of the forehead, or the vertex and the patient is asked in which ear the sound is heard. Usually, it is heard equally in both ears. It is lateralised to the worse ear in conductive deafness and to the better ear in sensorineural deafness. Option D. In Rinne’s test, air conduction of the ear is compared with its bone conduction. A vibrating tuning fork is placed on the patient’s mastoid, and when he stops hearing, it is brought beside the meatus. If he still hears, AC is more than BC. Alternatively, the patient is asked to compare the loudness of sound heard through air and bone conduction. Rinne's test is called positive when AC is longer or louder than BC. It is seen in normal persons or those having sensorineural deafness.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Rinne's test is negative in:", "options": [{"label": "A", "text": "Sensorineural Deafness", "correct": false}, {"label": "B", "text": "Acoustic Neuroma", "correct": false}, {"label": "C", "text": "Tympanosclerosis", "correct": true}, {"label": "D", "text": "Meniere's Disease", "correct": false}], "correct_answer": "C. Tympanosclerosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Tympanosclerosis Rinne’s test is negative in conductive deafness of more than 15 dB. In tympanosclerosis there is conductive deafness.</p>\n<p><strong>Highyeild:</strong></p><p>Interpretation Of Rinne’s Test Usually, AC is two times better than BC– Positive Rinne In conductive deafness – BC > AC → Negative Rinne In SNHL – AC > BC → Low positive Rinne In severe SNHL–BC>AC → False negative Rinne (Due to transcranial transmission of sounds to the normal ear)</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Rinne’s test is positive in SNHL. Option B. In acoustic neuroma also, there is SNHL, so Rinne’s will be positive. Option D. In Meniere’s disease, there is fluctuating SNHL, so Rinne’s will be positive.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Weber test is best elicited by:", "options": [{"label": "A", "text": "Placing the tuning fork on the mastoid process and comparing the bone conduction of the patient with that of the examiner", "correct": false}, {"label": "B", "text": "Placing the tuning fork on the vertex of the skull and determining the effect of gently occluding the auditory canal on the threshold of low frequencies", "correct": false}, {"label": "C", "text": "Placing the tuning fork on the mastoid process and comparing the bone conduction in the patient", "correct": false}, {"label": "D", "text": "Placing the tuning fork on the forehead and asking him to report in which ear he hears it better", "correct": true}], "correct_answer": "D. Placing the tuning fork on the forehead and asking him to report in which ear he hears it better", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Placing the tuning fork on the forehead and asking him to report in which ear he hears it better In the Weber test, a vibrating tuning fork is placed in the middle of the forehead, or the vertex and the patient is asked in which ear the sound is heard.</p>\n<p><strong>Highyeild:</strong></p><p>Weber Test In this test, a vibrating tuning fork is placed in the middle of the forehead, or the vertex and the patient is asked in which ear the sound is heard. Usually, it is heard equally in both ears. It is lateralised to the worse ear in conductive deafness and to the better ear in sensorineural deafness. Rinne's test Placing the tuning fork on mastoid process and bringing it beside the meatus, when patient stops hearing it on mastoid Weber's test Placing the tuning fork on forehead and asking him to report in which ear he hears better Absolute bone conduction Placing the tuning fork on mastoid process and comparing the bone conduction of the patient with that of examiner after occluding the meatus Schwabach's test Test same as absolute bone conduction but meatus is not occluded. Placing the tuning fork on the forehead and asking him to report in which ear he hears better</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "As an intern, you are posted for ENT. A male patient comes to OPD with decreased hearing. After a routine ENT examination, ENT PG took a tuning fork, performed a test, and told you to write Gelle’s test positive. Which of the following is true regarding Gelle’s test?", "options": [{"label": "A", "text": "Gelle’s positive in conductive hearing loss", "correct": false}, {"label": "B", "text": "Test is performed by keeping tunning fork in the vertex", "correct": false}, {"label": "C", "text": "Test is negative in otosclerosis", "correct": true}, {"label": "D", "text": "Ear has occluded alternatively during the test", "correct": false}], "correct_answer": "C. Test is negative in otosclerosis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Test is negative in otosclerosis In otosclerosis, the ossicular chain is fixed or disconnected . Gelle’s test is negative in it.</p>\n<p><strong>Highyeild:</strong></p><p>Gelle's Test It is a test of bone conduction and examines the effect of increased air pressure in the ear canal on the hearing. Usually, when air pressure is increased in the ear canal by Siegel’s speculum, it pushes the tympanic membrane and ossicles inwards, raises the intralabyrinthine pressure and causes immobility of the basilar membrane and decreased hearing. Still, no change in hearing is observed when the ossicular chain is fixed or disconnected. Gelle’s test is performed by placing a vibrating fork on the mastoid, while Siegel’s speculum changes the air pressure in the ear canal. Gelle’s test is positive in normal persons and those with sensorineural hearing loss. It is negative when the ossicular chain is fixed or disconnected.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A. Gelle’s test is positive for sensorineural hearing loss, not conductive hearing loss. O ption B. Gelle’s test is performed by placing a vibrating fork on the mastoid, not the vertex. Option D. Ear has occluded alternatively during the test is a false statement.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Tuning fork frequency used MOST commonly in the ENT clinic is likely to be which of the following?", "options": [{"label": "A", "text": "256 Hz", "correct": false}, {"label": "B", "text": "512 Hz", "correct": true}, {"label": "C", "text": "1024 Hz", "correct": false}, {"label": "D", "text": "2048 Hz", "correct": false}], "correct_answer": "B. 512 Hz", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>512 Hz Ideally, three frequencies are used 256 Hz, 512 Hz, and 1024 Hz. These three frequencies are used because they fall within the speech frequency range. The most preferred and commonly used is 512 Hz since tuning forks of higher frequency have shorter decay times, and those with lower frequency produce a sense of bone vibration . It is a f requency common in human speech.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In the Bing test, the sound increases and decreases on alternately compressing and releasing the external acoustic meatus. What does this indicate?", "options": [{"label": "A", "text": "Otosclerosis", "correct": false}, {"label": "B", "text": "Sensorineural Deafness", "correct": true}, {"label": "C", "text": "Adhesive Otitis Media", "correct": false}, {"label": "D", "text": "Chronic suppurative otitis media", "correct": false}], "correct_answer": "B. Sensorineural Deafness", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Sensorineural Deafness Bing test is a test of bone conduction that examines the effect of occlusion of the ear canal and the hearing. A v ibrating tuning fork is placed on the mastoid while the examiner alternately closes and opens the ear canal by pressing the tragus inwards. A normal person / one with sensorineural hearing loss hears louder when the ear canal is occluded and softer when the canal is open. A patient with conductive hearing loss will appreciate no change.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Otosclerosis is a type of conductive deafness. So hearing in the Bing test will remain the same. Option C. Adhesive Otitis Media also have the conductive type of deafness. Option D. Chronic suppurative otitis media also has the conductive type of deafness.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A child was treated for H. Influenza meningitis for six months. The most important investigation to be done before discharging the patient is:", "options": [{"label": "A", "text": "MRI", "correct": false}, {"label": "B", "text": "Brainstem evoked auditory response", "correct": true}, {"label": "C", "text": "Growth Screening Test", "correct": false}, {"label": "D", "text": "Psychotherapy", "correct": false}], "correct_answer": "B. Brainstem evoked auditory response", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Brainstem evoked auditory response The explanation for the given options: Influenza Type Meningitis is frequent in children between 3 and 12 months . The residual auditory deficit is a common complication . Since residual auditory deficit is a common complication of H. influenza meningitis , audiological tests to detect the deficit should be performed before discharging any patient suffering from H. influenza meningitis. The brainstem-evoked auditory response is the best test to detect hearing loss in children.</p>\n<p><strong>Highyeild:</strong></p><p>Bera/ Brainstem Evoked Response Audiometry Also called BAER or BAEP (brainstem auditory evoked response or potential), or BERA (brainstem evoked response audiometry) The function elicits brainstem responses to auditory stimulation by clicks or tone bursts. It is a non-invasive technique to find the integrity of central auditory pathways through the VIIIth nerve, pons and midbrain. It measures hearing sensitivity in the range of 1000–4000 Hz. A normal person produces seven waves in the first 10 ms. The first, third and fifth waves are the most stable and are used in measurements. The waves are studied for absolute , interwave latency (usually between wave I and V) and amplitude. Brainstem auditory evoked potentials Wave I Distal part of CN VIII Wave II Proximal part of CN VIII near the brainstem Wave III Cochlear nucleus Wave IV Superior olivary complex Wave V Lateral lemniscus Waves VI and VII Inferior colliculus</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A middle-aged woman presented with right-sided hearing loss; Rinne's test shows a positive result on the left side and a negative result on the right. Weber's test showed Lateralization to the left side. Diagnosis is:", "options": [{"label": "A", "text": "Right-sided conductive deafness", "correct": false}, {"label": "B", "text": "Right-sided severe sensorineural deafness", "correct": true}, {"label": "C", "text": "Left-sided sensorineural deafness", "correct": false}, {"label": "D", "text": "Left-sided conductive deafness", "correct": false}], "correct_answer": "B. Right-sided severe sensorineural deafness", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Right-sided severe sensorineural deafness Rinne's Test Negative on the right side means either there is: Conductive deafness of the Right side or Severe SNHL on the right side (leading to false negative Rinne test). To differentiate between the 2 conditions: Let us see the result of Weber's test: The patient complains of decreased hearing in the right ear, and Weber's test is lateralised to the left ear (as stated in the question) to the better ear. As discussed in the text: Weber's test is lateralised to the better ear in the case of SNHL. So, the diagnosis is right-sided severe SNHL.</p>\n<p><strong>Highyeild:</strong></p><p>Weber Test In this test, a vibrating tuning fork is placed in the middle of the forehead, or the vertex and the patient is asked in which ear the sound is heard. Usually, it is heard equally in both ears. It is lateralised to the worse ear in conductive deafness and to the better ear in sensorineural deafness.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Tone decay test is done for:", "options": [{"label": "A", "text": "Cochlear Deafness", "correct": false}, {"label": "B", "text": "Neural Deafness", "correct": true}, {"label": "C", "text": "Middle Ear Problem", "correct": false}, {"label": "D", "text": "Otosclerosis", "correct": false}], "correct_answer": "B. Neural Deafness", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Neural Deafness The tone decay test measures nerve fatigue ( neural deafness) and is used to detect retro cochlear lesions . A more than 25 dB decay is diagnostic of a retro cochlear lesion.</p>\n<p><strong>Highyeild:</strong></p><p>Method of doing the test and principle A continuous tone of 5 dB above the threshold in 500 Hz and 2000 Hz is given to the ear, and a person should be able to hear it for 60 sec. The result is expressed as dB, with which intensity must be increased so that the patient can hear the sound for 60 sec. If tone decay of >25 dB is present, it indicates retro-cochlear leison, e.g.-acoustic neuroma.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In the case of 8th nerve palsy of the right ear. The stapedial reflex will be-", "options": [{"label": "A", "text": "Absent on rt side and present on the left side", "correct": false}, {"label": "B", "text": "Absent on both sides", "correct": true}, {"label": "C", "text": "Present on the left side and absent on the right side", "correct": false}, {"label": "D", "text": "Present on both sides", "correct": false}], "correct_answer": "B. Absent on both sides", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Absent on both sides Stapedial reflex : Mediated by the 7th ( efferent ) and 8th ( afferent ) nerve, It is a Bilateral reflex. 7th nerve palsy – reflex absent only on the side of the lesion. 8th nerve palsy – reflex absent on both sides.</p>\n<p><strong>Highyeild:</strong></p><p>Stapedial Reflex It is based on the fact that a loud sound, 70–100 dB above the threshold of hearing of a particular ear, causes bilateral contraction of the stapedial muscles, which can be detected by tympanometry. Tone can be delivered to one ear, and the reflex is picked from the same or the contralateral ear. The reflex arc involved is Ipsilateral : CN VIII → ventral cochlear nucleus → CN VII nucleus ipsilateral stapedius muscle. Contralateral: CN VIII → ventral cochlear nucleus → contralateral medial superior olivary nucleus → contralateral CN VII nucleus → contralateral stapedius muscle. Acoustic reflex.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 22 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 8</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Tympanometry - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 8</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 8 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "B-type tympanogram is seen in:", "options": [{"label": "A", "text": "Serous Otitis Media", "correct": true}, {"label": "B", "text": "Ossicular Discontinuity", "correct": false}, {"label": "C", "text": "Otosclerosis", "correct": false}, {"label": "D", "text": "All of the above", "correct": false}], "correct_answer": "A. Serous Otitis Media", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Serous Otitis Media B-type tympanogram is seen in middle ear fluid or tympanic membrane perforation. So the correct answer is A.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option B. Ossicular Discontinuity Type Ad type curve. Option C. Otosclerosis has a Type As type curve.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In osteogenesis imperfecta, the tympanogram is:", "options": [{"label": "A", "text": "Flat", "correct": false}, {"label": "B", "text": "Negative compliance", "correct": false}, {"label": "C", "text": "High-Compliance", "correct": false}, {"label": "D", "text": "Low-Compliance", "correct": true}], "correct_answer": "D. Low-Compliance", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Low-Compliance Osteogenesis imperfecta is associated with otosclerosis. And in otosclerosis, there is Type As, i.e. Low compliance tympanogram.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Flat/Dome-shaped tympanogram is seen in the case of middle ear fluid or tympanic membrane perforation. Option B., Negative compliance tympanogram, is seen in the case of the retracted tympanic membrane. Option C. High-compliance tympanogram is seen in the case of ossicular discontinuity or laxed tympanic membrane.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which is the investigation of choice in assessing hearing loss in neonates?", "options": [{"label": "A", "text": "Impedance Audiometry", "correct": false}, {"label": "B", "text": "Brainstem evoked response audiometry (BERA)", "correct": true}, {"label": "C", "text": "Free Field Audiometry", "correct": false}, {"label": "D", "text": "Behavioral Audiometry", "correct": false}], "correct_answer": "B. Brainstem evoked response audiometry (BERA)", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Brainstem evoked response audiometry (BERA) Remember in children and infants: Best screening test for detecting hearing loss-Otoacoustic emission The best confirmatory test is BERA.</p>\n<p><strong>Highyeild:</strong></p><p>Brainstem auditory evoked potentials Wave I Distal part of CN VIII Wave II Proximal part of CN VIII near the brainstem Wave III Cochlear nucleus Wave IV Superior olivary complex Wave V Lateral lemniscus Waves VI and VII Inferior colliculus</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In normal adult wave V in BERA is generated from:", "options": [{"label": "A", "text": "Cochlear Nucleus", "correct": false}, {"label": "B", "text": "Superior Olivary Complex", "correct": false}, {"label": "C", "text": "Lateral Lemniscus", "correct": true}, {"label": "D", "text": "Inferior Colliculus", "correct": false}], "correct_answer": "C. Lateral Lemniscus", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lateral Lemniscus Wave V in BERA is generated from the inferior colliculus.</p>\n<p><strong>Highyeild:</strong></p><p>Brainstem auditory evoked potentials Wave I Distal part of CN VIII Wave II Proximal part of CN VIII near the brainstem Wave III Cochlear nucleus Wave IV Superior olivary complex Wave V Lateral lemniscus Waves VI and VII Inferior colliculus</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Cochlear nucleus produces wave III. Option B. Superior Olivary Complex produces wave IV. Option D. Inferior Colliculus produce wave VI and VII.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "The diagnosis in the audiogram shown below is as follows:", "options": [{"label": "A", "text": "Ototoxicity", "correct": false}, {"label": "B", "text": "Meniere Disease", "correct": false}, {"label": "C", "text": "Otosclerosis", "correct": true}, {"label": "D", "text": "Noise-induced hearing loss", "correct": false}], "correct_answer": "C. Otosclerosis", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003615636-QTDE046006IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Otosclerosis The above audiogram is of otosclerosis. There is a dip in the bone conduction curve. It is different at different frequencies but is maximum at 2000 Hz and is called Carhart’s notch.</p>\n<p><strong>Highyeild:</strong></p><p>Classic audiometric findings in Otosclerosis Low-frequency conductive hearing loss. Cahart’s notch. Type A or As tympanogram. Diphasic or absent acoustic reflex. Negative Rinne test.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 35-year-old woman with bilateral hearing loss for six years became profound with pregnancy. On a tympanogram, which of the following curve is obtained?", "options": [{"label": "A", "text": "Ad", "correct": false}, {"label": "B", "text": "As", "correct": true}, {"label": "C", "text": "B", "correct": false}, {"label": "D", "text": "C", "correct": false}], "correct_answer": "B. As", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>As Bilateral hearing loss getting worse in pregnancy is due to otosclerosis. Tympanogram, in this case, would be As type. It is a normal tympanogram with reduced compliance due to stapes fixation.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option A. Type Ad is seen in the case of ossicular discontinuity or laxed tympanic membrane. Option C. Type B is seen in the case of middle ear fluid or tympanic membrane perforation. Option D. Type C is seen in the case of the retracted tympanic membrane.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 15-year-old boy was brought in with ear pain and fullness complaints. On examination, you suspect Middle ear effusion with an intact eardrum. Which type of tympanogram should be seen in this patient?", "options": [{"label": "A", "text": "Type As", "correct": false}, {"label": "B", "text": "Type B", "correct": true}, {"label": "C", "text": "Type C", "correct": false}, {"label": "D", "text": "Type Ad", "correct": false}], "correct_answer": "B. Type B", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Type B B type tympnogram is Flat/Dome-shaped tympanogram. It is seen in the case of middle ear fluid or tympanic membrane perforation.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A- Type As is seen in the case of fixation of ossicles, i.e. otosclerosis or malleus. Option C- Type C is seen in the case of a retracted tympanic membrane. Option D- Type Ad is seen in the case of ossicular discontinuity or laxed tympanic membrane.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A young man presents with an accident leading to hearing loss in the right ear. On otoscopic examination, the tympanic membrane was intact pure tone audiometry that shows an air-bone gap of 55 dB in the right ear with normal cochlear reserve. Which of the following will be the likely tympanometry finding:", "options": [{"label": "A", "text": "As Type Tympanogram", "correct": false}, {"label": "B", "text": "Ad Type Tympanogram", "correct": true}, {"label": "C", "text": "B Type Tympanogram", "correct": false}, {"label": "D", "text": "C Type Tympanogram", "correct": false}], "correct_answer": "B. Ad Type Tympanogram", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Ad Type Tympanogram The question says Pure tone audiometry shows an air-bone gap of 55 dB in the right ear with normal cochlear reserve. The air-bone gap in pure tone audiometry measures total conductive deafness. Hence it means there is a conductive deafness of 55 dB in the right ear. Next, the question says - Patient has an intact tympanic membrane, so we must look for a cause of this 55 dB conductive deafness. Average hearing loss seen in different lesions of conductive apparatus 1. Complete obstruction of ear canal 2. Perforation of tympanic membrane 3. Ossicular interruption with intact drum 4. Ossicular interruption with perforation 5. Closure of oval window 30 dB 10-40 dB 54dB 10-25 dB 60 dB As it is clear from the above table- the tympanic membrane is intact, and a hearing loss of 55 dB is seen if the ossicular chain is disrupted. Hence it is a case of ossicular discontinuity. An ad type of tympanogram is seen in this.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. Type Tympanogram is seen in the case of fixation of ossicles, i.e. otosclerosis or malleus. Option C. B type of tympanogram is seen in the case of middle ear fluid or tympanic membrane perforation. Option D. C type of tympanogram is seen in the case of the retracted tympanic membrane.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 18 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 5</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Vestibular Function, Caloric Test & Fistula Tests - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 5</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 5 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "A patient comes with complaints of Vertigo and dizziness. After doing some clinical tests of vestibular function, you send the patient for the Fitzgerald Hallpike test in which the patient lies supine with head tilted 30 ° forwards, and the ear is irrigated for 40 sec, alternate with warm and cold water. What would you expect when irrigated with cold water?", "options": [{"label": "A", "text": "No Nystagmus", "correct": false}, {"label": "B", "text": "Nystagmus to opposite side", "correct": true}, {"label": "C", "text": "Nystagmus to same side", "correct": false}, {"label": "D", "text": "None of the above.", "correct": false}], "correct_answer": "B. Nystagmus to opposite side", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Nystagmus to opposite side Cold water induces nystagmus to the opposite side.</p>\n<p><strong>Highyeild:</strong></p><p>Cold water induces nystagmus to the opposite side and warm water to the same side. Remember mnemonic COWS : cold–opposite, warm–same.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A. If no nystagmus is elicited from any ear, the test is repeated with water at 20°C for 4 min before labelling the labyrinth dead. A gap of 5 min should be allowed between two ears. Option C. Warm water to the same side (remember mnemonic COWS: cold-opposite, warm-same). Depending on the response to the caloric test, we can find canal paresis or dead labyrinth, directional preponderance, i.e. nystagmus is more in one particular direction than in the other, or both canal paresis and directional preponderance.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 52-year-old male patient visited the Department of Otolaryngology complaining of headache, left hearing loss and severe dizziness, which had begun after a fall with traumatic brain injury three days earlier. There is no evidence of skull bone fracture in investigations. The patient presented a mixed horizontal-torsional right beating nystagmus in the spontaneous and gaze nystagmus test. The otoscopic examination was normal bilaterally. In addition, the Hennebert sign was present with positive nystagmus on the left. Hennebert sign is present in which condition?", "options": [{"label": "A", "text": "Meniere's Disease", "correct": true}, {"label": "B", "text": "Acoustic Neuroma", "correct": false}, {"label": "C", "text": "Vestibular Neuronitis", "correct": false}, {"label": "D", "text": "Head Trauma", "correct": false}], "correct_answer": "A. Meniere's Disease", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Meniere's Disease A false-positive fistula test (i.e. positive fistula test without a fistula) is seen in congenital syphilis and about 25% of cases of Ménière’s disease and is called the Hennebert sign. So out of the given options, the correct answer is option A.</p>\n<p><strong>Highyeild:</strong></p><p>HENNEBERT SIGN In congenital syphilis, it is due to hypermobile stapes on inflammatory fibrous bands between the stapes footplate and saccule or utricle. Ménière's disease is due to a distended saccule lying against the stapes footplate.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption B. Acoustic Neuroma has been classified in peripheral vestibular disorders as it arises from CN VIII within the internal acoustic meatus. It causes only unsteadiness or a vague sensation of motion. Severe episodic vertigo, as seen in the end-organ disease, is usually missing Other tumours of the temporal bone (e.g. glomus tumour, carcinoma of the external or middle ear and secondaries), destroy the labyrinth directly and causes vertigo. O ption C. Vestibular Neuronitis is characterised by severe vertigo of sudden onset with no cochlear symptoms. Attacks may last from a few days to 2 or 3 weeks. It is thought to occur due to a virus that attacks the vestibular ganglion. Management of acute attack is similar to that in Ménière’s disease. The disease is usually self-limiting Option D. Head Trauma Head injury may cause concussion of the labyrinth, completely disrupt the bony labyrinth or even the VIIIth nerve, or cause a perilymph fistula. Severe acoustic trauma, such as the one caused by an explosion, can also disturb the vestibular end organs and result in vertigo.</p>\n<p><strong>Extraedge:</strong></p><p>Hennebert’s phenomenon - Dysequilibrium following nose blowing or lifting a heavy object. Seen in perilymph fistula ( do not confuse it with Hennebert’s sign ).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 50-year-old female patient presented with complaints of transient vertigo often induced during ear cleaning. She had history of chronic suppurative otitis media with cholesteatoma. What would be the probable diagnosis?", "options": [{"label": "A", "text": "Circumscribed Labyrinthitis", "correct": true}, {"label": "B", "text": "Diffuse Serous Labyrinthitis", "correct": false}, {"label": "C", "text": "Diffuse Suppurative Labyrinthitis", "correct": false}, {"label": "D", "text": "Acoustic Neuroma", "correct": false}], "correct_answer": "A. Circumscribed Labyrinthitis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Circumscribed Labyrinthitis In Circumscribed Labyrinthitis, a part of the membranous labyrinth is exposed and becomes sensitive to pressure changes . The patient complains of transient vertigo often induced by pressure on the tragus, cleaning the ear or while performing the Valsalva manoeuvre.</p>\n<p><strong>Highyeild:</strong></p><p>Circumscribed Labyrinthitis (Fistula of Labyrinth) There is thinning or erosion of the bony capsule of the labyrinth, Usually of the horizontal semicircular canal. The causes are Chronic suppurative otitis media with cholesteatoma is the most common cause. Neoplasms of the middle ear, e.g. carcinoma or glomus tumour. Surgical or accidental trauma to the Clinical Features - A part of the membranous labyrinth is exposed and sensitive to pressure changes. Patients complain of transient vertigo often induced by pressure on the tragus, cleaning the ear or while performing the Valsalva manoeuvre.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption B. In Diffuse Serous Labyrinthitis, Mild cases complain of vertigo and nausea, but in severe cases, vertigo is worse with marked nausea, vomiting and even spontaneous nystagmus. The quick component of nystagmus is towards the affected ear. As the inflammation is diffused, the cochlea is also affected by some degree of sensorineural hearing loss. Option C. In Diffuse Suppurative Labyrinthitis, there is severe vertigo with nausea and vomiting due to acute vestibular failure. Spontaneous nystagmus will be observed with its quick component towards the healthy side. The patient is markedly toxic. There is a total loss of hearing. Relief from vertigo is seen after 3-6 weeks due to adaptation. Option D. Acoustic Neuroma has been classified in peripheral vestibular disorders as it arises from CN VIII within the internal acoustic meatus. It causes only unsteadiness or a vague sensation of motion. Severe episodic vertigo, as seen in the end-organ disease, is usually missing Other tumours of the temporal bone (e.g. glomus tumour, carcinoma of the external or middle ear and secondaries), destroy the labyrinth directly and causes vertigo.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient was admitted to the hospital with vertigo, dizziness and vomiting complaints. As a part of the lab test of vestibular function, the patient lies supine with head tilted 30°forward and the ear is rinsed for 40 sec alternatively with water at 30 ° c and 44° c and eyes are observed for the appearance of nystagmus till its endpoint. What test has been done here?", "options": [{"label": "A", "text": "Modified Kobrak Test", "correct": false}, {"label": "B", "text": "Fitzgerald Hallpike Test", "correct": true}, {"label": "C", "text": "Electronystagmography", "correct": false}, {"label": "D", "text": "Optokinetic Test", "correct": false}], "correct_answer": "B. Fitzgerald Hallpike Test", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Fitzgerald Hallpike Test The test described above is Fitzgerald-Hallpike Test / Bithermal Caloric Test.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option A . Modified Kobrak Test It is a quick office procedure. The patient is seated with head tilted 60° backwards to place the horizontal canal in a vertical position. The ear is irrigated with ice water for 60 s, first with 5 ml and if there is no response,10,29 and 40 ml . Normally Nystagmus beating towards the opposite ear will be seen with 5 ml of ice water. If response is seen with increased quantities of water between 5 and 40, labyrinth is considered hypoactive. No response to 40 ml of watering indicates a dead labyrinth. Option C. Electronystagmography is a method of detecting and recording nystagmus, which is spontaneous or induced by caloric, positional, rotational or optokinetic stimulus. The test depends on the presence of corneoretinal potentials, which are recorded by placing electrodes at suitable places around the eyes. The test is also helpful to detect nystagmus, which is not seen with the naked eye. It also permits to keep a permanent record of nystagmus. Option D . In the Optokinetic test, the patient is asked to follow a series of vertical stripes on a drum, moving first from right to left and then from left to right. Usually, it produces nystagmus with a slow components in the direction of moving stripes and a fast components in the opposite direction.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "ENT doctor has done a a fistula test for a patient's doubtful case of post-stapedectomy fistula. Which among the following result will you suspect?", "options": [{"label": "A", "text": "Absent", "correct": false}, {"label": "B", "text": "Positive", "correct": true}, {"label": "C", "text": "A False Positive", "correct": false}, {"label": "D", "text": "A False Negative.", "correct": false}], "correct_answer": "B. Positive", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Positive It is positive when there is erosion of the horizontal semicircular canal as in cholesteatoma or a surgically created window in the horizontal canal (fenestration operation), an abnormal opening in the oval window (post-stapedectomy fistula) or the round window (rupture of round window membrane). A positive fistula also implies that the labyrinth is still functioning.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption A . It is absent when the labyrinth is dead. O ption C. A false positive fistula test (i.e. positive fistula test without a fistula) is seen in congenital syphilis and about 25% of cases of Meniere’s disease (Hennebert's sign). In congenital syphilis, the stapes footplate is hypermobile, while in Meniere’s disease, it is due to the fibrous bands connecting the utricular macula to the stapes footplate. In both these conditions, movements of stapes result in stimulation of the utricular macula. Option D . A false negative fistula test is also seen when cholesteatoma covers the site of the fistula and does not allow pressure changes to be transmitted to the labyrinth.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 15 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 17</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Vocal Cord Paralysis - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 17</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 17 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "All are true about Reinke Odema except:", "options": [{"label": "A", "text": "Known as smoker larynx", "correct": false}, {"label": "B", "text": "Presents With Hoarseness", "correct": false}, {"label": "C", "text": "Speech therapy and voice rest are the mainstays of treatment", "correct": false}, {"label": "D", "text": "There is bilateral symmetrical swelling of vocal cords.", "correct": true}], "correct_answer": "D. There is bilateral symmetrical swelling of vocal cords.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>There is bilateral symmetrical swelling of vocal cords. Reinke's oedema is bilateral symmetrical swelling of the whole membranous part of vocal cords , most often seen in middle-aged men and women.</p>\n<p><strong>Highyeild:</strong></p><p>REINKE’S EDEMA It is bilateral symmetrical swelling of the whole membranous part of vocal cords, most often seen in middle-aged men and women. This is due to oedema of subepithelial space. Chronic irritation of vocal cords due to misuse of the voice, heavy smoking, chronic sinusitis</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Reinke’s oedema, also known as smoker larynx, is a true statement. Option: B. Hoarseness is the common symptom in Reinke’s oedema. Option: C. Speech therapy and voice rest are the mainstay of treatment is also a true statement.</p>\n<p><strong>Extraedge:</strong></p><p>C / F: Hoarseness is the common symptom. Low-pitched and rough voice. Rx: - Decortication of vocal cords Voice rest Speech therapy</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Type 3 laser cordectomy is -", "options": [{"label": "A", "text": "Subepithelial", "correct": false}, {"label": "B", "text": "Subligamental", "correct": false}, {"label": "C", "text": "Transmuscular", "correct": true}, {"label": "D", "text": "Total Cordectomy", "correct": false}], "correct_answer": "C. Transmuscular", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Transmuscular Type III laser cordectomy is a Transmuscular cordectomy.</p>\n<p><strong>Highyeild:</strong></p><p>TYPES OF LASER CORDECTOMY Type I - Subepithelial cordectomy Type II - Subligamental cordectomy Type III- Transmuscular cordectomy Type IV - Total cordectomy Type Va- Extended cordectomy encompasses the contralateral vocal fold and the anterior commissure. Type Vb- extended cordectomy, which includes the arytenoid Type Vc - extended cordectomy, which encompasses the subglottis Type Vd- extended cordectomy, which includes the ventricle</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Subepithelial cordectomy is Type I cordectomy. Option: B. Subligamental cordectomy is Type II cordectomy. Option: D. Total Cordectomy is Type IV cordectomy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Hypopharynx is clinically subdivided into 3 parts except:", "options": [{"label": "A", "text": "Pyriform Fossa", "correct": false}, {"label": "B", "text": "Post Cricoid region", "correct": false}, {"label": "C", "text": "Posterior Pharyngeal Wall", "correct": false}, {"label": "D", "text": "Cricopharynx", "correct": true}], "correct_answer": "D. Cricopharynx", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Cricopharynx Cricopharynx is not the clinical subdivision of the hypopharynx.</p>\n<p><strong>Highyeild:</strong></p><p>Divisions of Hypopharynx Pyriform sinus (fossa) —Bounded by: Superiorly – Pharyngoepiglottic folds Inferiorly – Lower border of the cricoid Laterally – Thyrohyoid membrane and thyroid cartilage Medially – Aryepiglottic fold. Postcricoid region: – Lies between the upper and lower border of the cricoid lamina – Commonest site of carcinoma in females suffering from Plummer-Vinson syndrome. Posterior pharyngeal wall: – Extends from the hyoid bone to the cricoarytenoid joint.</p>\n<p><strong>Extraedge:</strong></p><p>Pyriform sinus drain into the upper jugular chain. Lymphatics of the posterior wall terminate in the lateral pharyngeal or parapharyngeal nodes and thence to the deep cervical lymph nodes. Lymphatics of postcricoid region also drain into the parapharyngeal nodes but may also drain into nodes of supraclavicular and paratracheal chain.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A patient has difficulty in breathing. On examination, there is a unilateral lengthening of the vocal cord in the adducted position. The patient is not able to abduct the vocal. Which of the following muscles is likely to be involved?", "options": [{"label": "A", "text": "Posterior Cricoarytenoid", "correct": true}, {"label": "B", "text": "Thyroarytenoid", "correct": false}, {"label": "C", "text": "Cricothyroid", "correct": false}, {"label": "D", "text": "Interarytenoid", "correct": false}], "correct_answer": "A. Posterior Cricoarytenoid", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Posterior Cricoarytenoid In the question, the patient cannot abduct the vocal cord, which is the action of the Posterior cricoarytenoid.</p>\n<p><strong>Highyeild:</strong></p><p>MUSCLES ACTING ON THE LARYNX Abductors: Posterior cricoarytenoid Adductors: Lateral cricoarytenoid Interarytenoid (transverse arytenoid) Thyroarytenoid (external part) Tensors: Cricothyroid Vocalis (internal part of thyroarytenoid)</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Thyroarytenoid is an adductor of the vocal cord. Option: C. Cricothyroid is a tensor of the vocal cord. Option: D. Interarytenoid is also an adductor of the vocal cord.</p>\n<p><strong>Extraedge:</strong></p><p>Openers of laryngeal inlet: Thyroepiglottic (part of thyroarytenoid) Closers of laryngeal inlet: Interarytenoid (oblique part). Aryepiglottic (posterior oblique part of interarytenoids)</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which one of the following lesions is dangerous to life?", "options": [{"label": "A", "text": "Bilateral Adductor Paralysis", "correct": false}, {"label": "B", "text": "Bilateral Abductor Paralysis", "correct": true}, {"label": "C", "text": "U/L Abductor Paralysis", "correct": false}, {"label": "D", "text": "U/L adductor paralysis", "correct": false}], "correct_answer": "B. Bilateral Abductor Paralysis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Bilateral Abductor Paralysis As both the cords lie in the median or paramedian position, the airway is inadequate, causing dyspnoea and stridor . Many cases of bilateral abductor paralysis require tracheostomy as an emergency procedure or when they develop upper respiratory tract infections. Hence it is life-threatening.</p>\n<p><strong>Highyeild:</strong></p><p>Position of vocal cords in health and disease. Situation in Position of the cord Location of the cord from midline Health Disease Median Midline Phonation RLN paralysis Paramedian 1.5 mm Strong whisper RLN paralysis Intermediate (cadaveric) 3.5 mm. This is neutral position of cricoarytenoid joint. Abduction and adduction take place from this position → Paralysis of both recurrent and superi laryngeal nerves Gentle abduction 7 mm Quiet respiration Paralysis of adducto Full abduction 9.5 mm Deep inspiration →</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A . In B/L Adductor paralysis Position of the cord will be B/L Cadaveric. Option: C. In U/L Abductor Paralysis, vocal cords will be in the Paramedian position. Option: D. In U/L adductor paralysis Position of the cord will be in the Cadaveric position.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following is considered supraglottic carcinoma larynx T3?", "options": [{"label": "A", "text": "Tumour limited to one subsite of supraglottis with normal vocal cord mobility.", "correct": false}, {"label": "B", "text": "Tumour invades mucosa of more than one adjacent subsites of the supraglottis, glottis, or region outside the supraglottis.", "correct": false}, {"label": "C", "text": "Vocal Cord Immobility", "correct": true}, {"label": "D", "text": "Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures.", "correct": false}], "correct_answer": "C. Vocal Cord Immobility", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Vocal Cord Immobility T3 is a tumour limited to the larynx with vocal cord fixation and/or invades any of the following post-cricoid area, pre-epiglottic tissues, paraglottic space and/or minor thyroid cartilage invasion.</p>\n<p><strong>Highyeild:</strong></p><p>TNM classification of cancer larynx TNM classification of cancer larynx (American joint committee on Cancer, 2002) Supraglottis T₁ Tumour limited to one subsite of supraglottis with normal vocal cord mobility. Τ 2 Tumour invades mucosa of more than one adjacent subsites of supraglottis or glottis or region outside the supraglottis (e.g., mucosa of base of tongue, vallecula, medial wall of pyriform sinus) without fixation of the larynx. T 3 Tumour limited to larynx with vocal cord fixation and/or invades any of the following: postcricoid area, pre-epiglottic tissues, paraglottic space and/or minor thyroid cartilage invasion. T 4 a Tumour invades through the thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscle of tongue, strap muscles, thyroid or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures. Glottis T₁ Tumour limited to vocal cord(s) (may involve anterior or posterior commissures) with normal mobility. T₁a Tumour limited to one vocal cord. T₁b Tumour involves both vocal cords. Τ 2 Tumour extends to supraglottis and/or subglottis, and/or with impaired vocal cord mobility. T 3 Tumour limited to the larynx with vocal cord fixation and/or invades paraglottic space and/or minor thyroid cartilage erosion. T 4 a Tumour invades through thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscles of the tongue, strap muscles, thyroid, or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures. Subglottis T 1 Tumour limited to the subglottis. T 2 Tumour extends to vocal cord(s) with normal or impaired mobility. T 3 Tumour limited to larynx with vocal cord fixation. T 4 a Tumour invades cricoid or thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscle of tongue, strap muscles, thyroid or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures. Regional lymph nodes (N) N X Regional lymph nodes cannot be assessed. N 0 No regional lymph node metastasis. N 1 Metastasis in a single ipsilateral lymph node, 3 cm or less in greatest dimension. N 2 Metastasis in a single ipsilateral lymph node, more than 3 cm but not more than 6 cm in greatest dimension, or multiple ipsilateral lymph nodes, none more than 6 cm in greatest dimension, or bilateral or contralateral lymph nodes, none more than 6 cm in greatest dimension. N 2 a Metastasis in a single ipsilateral lymph node more than 3 cm but not more than 6 cm in greatest dimension. N 2 b Metastasis in multiple ipsilateral lymph nodes, none more than 6 cm in greatest dimension N 2 c Metastasis in bilateral or contralateral lymph nodes, none more than 6 cm in greatest dimension N 3 Metastasis in a lymph node more than 6 cm in greatest dimension. Distant metastasis (M) M X Distant metastasis cannot be assessed. M 0 No distant metastasis. M 1 Distant metastasis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Tumour limited to one subsite of supraglottis with normal vocal cord mobility belongs to T1. Option: B. Tumour invades mucosa of more than one adjacent subsites of supraglottis or glottis or region outside the supraglottis belonging to T2. Option: D. Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures belonging to T4b</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All Are True About This Condition Except:", "options": [{"label": "A", "text": "Bilateral In Nature", "correct": false}, {"label": "B", "text": "Size is less than a pinhead", "correct": false}, {"label": "C", "text": "Removal is done by M.L.S by wide excision of underlying ligament", "correct": true}, {"label": "D", "text": "Speech therapy is essential to prevent their recurrence", "correct": false}], "correct_answer": "C. Removal is done by M.L.S by wide excision of underlying ligament", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003604366-QTDE083008IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Removal is done by M.L.S by wide excision of underlying ligament The given Image shows Vocal Nodules, Aka Screamer's or Singer’s nodules. Treatment is conservative, and not Removal by wide excision of the underlying ligament.</p>\n<p><strong>Highyeild:</strong></p><p>VOCAL/SCREAMER’S/SINGER’S NODULE They appear symmetrically on the free edge of the vocal cord at the junction of anterior 1/3rd and posterior 2/3rd, as this is the area for maximum vibration of the cord. Their size varies from that of a pin-head to half a pea. They result from vocal trauma when a person speaks in unnatural low tones for prolonged periods or at high intensities. They mainly affect teachers, actors, vendors or pop singers. They are also seen in school-going children who are too assertive and talkative. Pathologically, trauma to the vocal cord in the form of vocal abuse or misuse causes oedema and haemorrhage in the submucosal space. This undergoes hyalinisation and fibrosis. The overlying epithelium also undergoes hyperplasia forming a nodule. Patients with vocal nodules complain of hoarseness. Vocal fatigue and pain in the neck on prolonged phonation are other common symptoms.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Bilateral in Nature is a true statement. Option: B. Their size varies from that of a pin-head to half a pea is a true statement. Option: D. Speech therapy is essential to prevent their recurrence is also a true statement.</p>\n<p><strong>Extraedge:</strong></p><p>Early cases of vocal nodules can be treated conservatively by educating the patient on the proper use of voice. Surgery only for large nodules Speech therapy and re-education in voice production are essential to prevent their recurrence.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Position of the vocal cord in a cadaver is:", "options": [{"label": "A", "text": "Median", "correct": false}, {"label": "B", "text": "Paramedian", "correct": false}, {"label": "C", "text": "Intermediate", "correct": true}, {"label": "D", "text": "Full Abduction", "correct": false}], "correct_answer": "C. Intermediate", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Intermediate In the cadaveric state , the vocal cord's position is intermediate (i.e. the, equal amount of adduction and abduction).</p>\n<p><strong>Highyeild:</strong></p><p>Position of vocal cords in health and disease. Situation in Position of the cord Location of the cord from midline Health Disease Median Midline Phonation RLN paralysis Paramedian 1.5 mm Strong whisper RLN paralysis Intermediate (cadaveric) 3.5 mm. This is neutral position of cricoarytenoid joint. Abduction and adduction take place from this position → Paralysis of both recurrent and superi laryngeal nerves Gentle abduction 7 mm Quiet respiration Paralysis of adducto Full abduction 9.5 mm Deep inspiration →</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. The position of vocal cords is median in phonation and RLN paralysis. Option: B. The position of vocal cords is paramedian in a strong whisper and RLN paralysis. Option: D. The position of vocal cords is in full abduction in deep inspiration.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Paralysis of recurrent laryngeal nerve true is:", "options": [{"label": "A", "text": "Common on the Left side.", "correct": true}, {"label": "B", "text": "Idiopathic in most cases.", "correct": false}, {"label": "C", "text": "Cord will be lateral in position.", "correct": false}, {"label": "D", "text": "Speech therapy given", "correct": false}], "correct_answer": "A. Common on the Left side.", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Common on the Left side. Paralysis of the recurrent laryngeal nerve is more common on the left side than right side because of the longer and more convoluted course of the left recurrent laryngeal nerve.</p>\n<p><strong>Highyeild:</strong></p><p>U/L RLN PALSY Clinical Features Asymptomatic in 1/3rd of cases In the rest of the patients, there may be some voice problem, i.e. dysphonia—the voice is hoarse and weak with use. This gradually improves with time due to compensation by the healthy cord which crosses the midline to meet the paralysed one. Generally, no speech therapy is required.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Most unilateral vocal cord paralysis is secondary to surgery, not idiopathic. Option: C. Unilateral injury to recurrent laryngeal nerve leads to ipsilateral paralysis of all intrinsic muscles except the cricothyroid (a vocal cord adductor). Thus, the vocal cord assumes a median or paramedian position that does not move laterally on deep inspiration. Option: D. Generally, no speech therapy is required</p>\n<p><strong>Extraedge:</strong></p><p>RLN TRIANGLE (OF LORE) It is bounded medially by trachea and oesophagus, laterally by retracted strap muscles, and superiorly by the lower pole of thyroid. Its apex is directed interiorly at thoracic inlet. RLN runs through this triangle from lateral to medial side on the right and straight up along tracheoesophageal groove on the left.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "True statement about Infraglottic carcinoma larynx:", "options": [{"label": "A", "text": "Commonly spreads to mediastinal nodes", "correct": true}, {"label": "B", "text": "Second most common carcinoma", "correct": false}, {"label": "C", "text": "Most common carcinoma", "correct": false}, {"label": "D", "text": "Spreads to submental nodes", "correct": false}], "correct_answer": "A. Commonly spreads to mediastinal nodes", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Commonly spreads to mediastinal nodes Lymphatic spread of subglottic carcinoma larynx occurs to prelaryngeal, pre-tracheal, paratracheal and lower jugular nodes (i.e. mediastinal nodes).</p>\n<p><strong>Highyeild:</strong></p><p>SUBGLOTTIC CARCINOMA Subglottic cancer is the rarest of laryngeal cancer. The earliest presentation is a globus or foreign body sensation in the throat followed by stridor or laryngeal obstruction.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Subglottic cancer is the rarest laryngeal cancer, not the second most common one. Option: C- Subglottic cancer is the rarest laryngeal cancer, not the most common. Option: D. Lymphatic spread of subglottic carcinoma larynx occurs to prelaryngeal, pre-tracheal, paratracheal and lower jugular nodes (i.e. mediastinal nodes), not the submental nodes.</p>\n<p><strong>Extraedge:</strong></p><p>Hoarseness is a late feature and occurs due to the involvement of the glottis or recurrent laryngeal nerve. Lymphatic spread occurs in prelaryngeal, pre-tracheal, paratracheal and lower jugular nodes (i.e. mediastinal nodes).</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "For a tumour with normal mobility of vocal cords, treatment is:", "options": [{"label": "A", "text": "Surgery", "correct": false}, {"label": "B", "text": "Chemotherapy", "correct": false}, {"label": "C", "text": "Radiotherapy", "correct": true}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "C. Radiotherapy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Radiotherapy Radiotherapy is the treatment of choice for vocal cord cancer with normal mobility. Normal cord mobility suggests that growth is only limited to the surface and belongs to either stage T1 or T2.</p>\n<p><strong>Highyeild:</strong></p><p>Normal cord mobility suggests that growth is only limited to the surface and belongs to either stage T1 or T2. TOC for stage T1 of glottic carcinoma - radiotherapy. TOC for stage T2 of glottic carcinoma - depends on the mobility of the cord</p>\n<p><strong>Extraedge:</strong></p><p>TNM classification of cancer larynx (American joint committee on cancer, 2002) Supraglottis T₁ Tumour limited to one subsite of supraglottis with normal vocal cord mobility. Τ 2 Tumour invades mucosa of more than one adjacent subsites of supraglottis or glottis or region outside the supraglottis (e.g., mucosa of base of tongue, vallecula, medial wall of pyriform sinus) without fixation of the larynx. T 3 Tumour limited to larynx with vocal cord fixation and/or invades any of the following: postcricoid area, pre-epiglottic tissues, paraglottic space and/or minor thyroid cartilage invasion. T 4 a Tumour invades through the thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscle of tongue, strap muscles, thyroid or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures. Glottis T₁ Tumour limited to vocal cord(s) (may involve anterior or posterior commissures) with normal mobility. T₁a Tumour limited to one vocal cord. T₁b Tumour involves both vocal cords. Τ 2 Tumour extends to supraglottis and/or subglottis, and/or with impaired vocal cord mobility. T 3 Tumour limited to the larynx with vocal cord fixation and/or invades paraglottic space and/or minor thyroid cartilage erosion. T 4 a Tumour invades through thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscles of the tongue, strap muscles, thyroid, or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures. Subglottis T 1 Tumour limited to the subglottis. T 2 Tumour extends to vocal cord(s) with normal or impaired mobility. T 3 Tumour limited to larynx with vocal cord fixation. T 4 a Tumour invades cricoid or thyroid cartilage and/or invades tissues beyond the larynx (e.g., trachea, soft tissues of neck including deep extrinsic muscle of tongue, strap muscles, thyroid or oesophagus). T 4 b Tumour invades prevertebral space, encases carotid artery or invades mediastinal structures. Regional lymph nodes (N) N X Regional lymph nodes cannot be assessed. N 0 No regional lymph node metastasis. N 1 Metastasis in a single ipsilateral lymph node, 3 cm or less in greatest dimension. N 2 Metastasis in a single ipsilateral lymph node, more than 3 cm but not more than 6 cm in greatest dimension, or multiple ipsilateral lymph nodes, none more than 6 cm in greatest dimension, or bilateral or contralateral lymph nodes, none more than 6 cm in greatest dimension. N 2 a Metastasis in a single ipsilateral lymph node more than 3 cm but not more than 6 cm in greatest dimension. N 2 b Metastasis in multiple ipsilateral lymph nodes, none more than 6 cm in greatest dimension N 2 c Metastasis in bilateral or contralateral lymph nodes, none more than 6 cm in greatest dimension N 3 Metastasis in a lymph node more than 6 cm in greatest dimension. Distant metastasis (M) M X Distant metastasis cannot be assessed. M 0 No distant metastasis. M 1 Distant metastasis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are true about the structure marked with an arrow except:", "options": [{"label": "A", "text": "They are known as true vocal cords", "correct": false}, {"label": "B", "text": "They are lined by squamous epithelium", "correct": false}, {"label": "C", "text": "Lymphatic drainage of vocal cords goes to the upper deep cervical lymph nodes", "correct": true}, {"label": "D", "text": "Part of Glottis", "correct": false}], "correct_answer": "C. Lymphatic drainage of vocal cords goes to the upper deep cervical lymph nodes", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003604494-QTDE083016IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Lymphatic drainage of vocal cords goes to the upper deep cervical lymph nodes The structure marked with an arrow is t rue vocal cords. True vocal cords do not have any lymphatic drainage .</p>\n<p><strong>Highyeild:</strong></p><p>VOCAL CORDS Stratified squamous epithelium lines the vocal cord. It overlies lamina propria, which consists of three layers: superficial layer (or Reinke’s space) intermediate layer deep layer. Intermediate and deep layers together form the vocal ligament. Glottis acts as a watershed line for Above it, the supraglottis drains into the upper and the middle deep cervical lymph nodes. Below is the subglottis draining into the lower deep cervical and mediastinal lymph nodes. True vocal cords do not have any lymphatic drainage.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. They are known as true vocal cords is a true statement. Option: B. They are lined by squamous epithelium is also a true statement. Option: D. True vocal cords lie in the glottis.</p>\n<p><strong>Extraedge:</strong></p><p>The whole of the larynx is lined by ciliated columnar epithelium, except for true vocal cords lined by stratified squamous epithelium. Everything above the vocal cords is a part of the supraglottis, and everything 1cm below the level of the vocal cords constitutes the subglottis.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "All are true about the laryngoscopy except:", "options": [{"label": "A", "text": "Direct laryngoscopy is done in the operating room.", "correct": false}, {"label": "B", "text": "On direct laryngoscopy, the larynx is seen as an inverted Image", "correct": true}, {"label": "C", "text": "On indirect laryngoscopy, the larynx is seen as an inverted Image.", "correct": false}, {"label": "D", "text": "Vocal cord movements can be assessed in indirect mirror laryngoscopy", "correct": false}], "correct_answer": "B. On direct laryngoscopy, the larynx is seen as an inverted Image", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>On direct laryngoscopy, the larynx is seen as an inverted Image On direct laryngoscopy, the larynx is seen directly . An inverted image is not seen.</p>\n<p><strong>Highyeild:</strong></p><p>Direct Laryngoscopy → Directly inserted in to the Larynx, and seen directly Differences between indirect mirror laryngoscopy examination and direct laryngoscopy examinations Features Indirect mirror laryngoscopy Direct laryngoscopy Foreshortening of larynx in anteroposterior dimension Yes No Ventricle seen separating true and false vocal cords No Yes What is seen? Inverted image (anterior looks posterior) Larynx is seen directly Appearance of vocal cords Flat and white Slightly rounded and pin Vocal cords movement Can be assessed Cannot be assessed wh under the effect of musc Subglottic region Not assessed properly Can be assessed better Procedure OPD Usually in operation room</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Direct laryngoscopy is done in the operating room, not in OPD. Option: C. On indirect laryngoscopy, the larynx is seen as an inverted Image is a true statement. Option: D. Vocal cord movements can be assessed in indirect mirror laryngoscopy, which is also true.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A middle-aged male comes to the outpatient department (OPD) with the only complaint of hoarseness of voice for the past two years. He has been a chronic smoker for 30 years. A reddish area of mucosal irregularity overlying a portion of both cords was seen on examination. Management would include all except", "options": [{"label": "A", "text": "Cessation of Smoking", "correct": false}, {"label": "B", "text": "Bilateral Cordectomy", "correct": true}, {"label": "C", "text": "Micro laryngeal surgery for biopsy", "correct": false}, {"label": "D", "text": "Regular Follow-Up", "correct": false}], "correct_answer": "B. Bilateral Cordectomy", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Bilateral Cordectomy Middle-aged man + Chronic smoking + Hoarseness of voice + Bilateral reddish area of mucosal irregularity on cords. All these indicate that it is either pachydermia laryngitis or an early carcinoma. Bilateral cordectomy is not required even if it is glottic cancer because radiotherapy treats the early stages of it.</p>\n<p><strong>Highyeild:</strong></p><p>PACHYDERMA LARYNGIS It is a form of chronic hypertrophic laryngitis affecting the posterior part of the larynx in the region of the inter-arytenoid and posterior part of the vocal cords. Clinically, the patient presents with hoarseness or husky voice and irritation in the throat. Indirect laryngoscopy reveals heaping up of red or grey granulation tissue in the inter arytenoid region and posterior thirds of vocal cords; the latter sometimes shows ulceration due to constant hammering of vocal processes as in talking, forming what is called a “ contact ulcer .” The condition is bilateral and symmetrical. It does not undergo malignant change. However, a lesion biopsy is essential to differentiate the lesion from carcinoma and tuberculosis.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Smoking is a causative factor and should be stopped in either condition. Option: C. Biopsy can distinguish both conditions, so option \"c\" is correct . Option:D. Regular follow-up is a must in either of the conditions.</p>\n<p><strong>Extraedge:</strong></p><p>It is mainly seen in men who indulge in excessive alcohol and smoking. Treatment is the removal of granulation tissue under the operating microscope which may require repetition, control of acid reflux and speech therapy.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Rhinolalia clausa is associated with all of the following except:", "options": [{"label": "A", "text": "Allergic Rhinitis", "correct": false}, {"label": "B", "text": "Palatal Paralysis", "correct": true}, {"label": "C", "text": "Adenoids", "correct": false}, {"label": "D", "text": "Nasal Polyps", "correct": false}], "correct_answer": "B. Palatal Paralysis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Palatal Paralysis Rhinolalia clausa lacks nasal resonance (hyponasality). It is seen in conditions that block the nose or nasopharynx. Palatal paralysis will lead to hypernasality and not hyponasality.</p>\n<p><strong>Highyeild:</strong></p><p>Causes of hyponasality and hypernasality. Hyponasality Hypernasality Common cold Velopharyngeal insufficiency Nasal allergy Congenitally short soft palate Nasal polypi Submucous palate Nasal growth Large nasopharynx Adenoids Cleft of soft palate Nasopharyngeal mass Paralysis of soft palate Familial speech pattern Postadenoidectomy Habitual Oronasal fistula Familial speech pattern Habitual speech pattern</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. It may be seen in allergic rhinitis. Option: C. Rhinolalia clausa may also be seen in adenoids. Option: D. Rhinolalia clausa may also be seen in nasal polyps.</p>\n<p><strong>Extraedge:</strong></p><p>It is a disorder of fluency of speech and consists of hesitation to start, repetitions, prolongations or blocks in the flow of speech. When well established, a stutterer may develop secondary mannerisms such as facial grimacing, eye blink and abnormal head movements.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Which of the following conditions result due to birth trauma?", "options": [{"label": "A", "text": "Congenital Laryngeal Stridor", "correct": false}, {"label": "B", "text": "Congenital vocal cord paralysis", "correct": true}, {"label": "C", "text": "Congenital Subglottic Stenosis", "correct": false}, {"label": "D", "text": "Laryngo – oesophageal cleft", "correct": false}], "correct_answer": "B. Congenital vocal cord paralysis", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Congenital vocal cord paralysis Congenital vocal cord paralysis results from birth trauma when a recurrent laryngeal nerve is stretched during breech or forceps delivery.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Congenital laryngeal stridor is laryngomalacia that develops within the first 2 weeks of life and is characterised by excessive flaccidity of supraglottic larynx. Option: C. Congenital subglottic stenosis is due to abnormal thickening of cricoid cartilage or fibrous tissue below vocal cords. Option: D. Laryngo – oesophageal cleft is due to failure of fusion of cricoid lamina.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In B/L abductor palsy position of vocal cords is:", "options": [{"label": "A", "text": "Cadaveric", "correct": false}, {"label": "B", "text": "Paramedian", "correct": true}, {"label": "C", "text": "Median", "correct": false}, {"label": "D", "text": "Gentle Abduction", "correct": false}], "correct_answer": "B. Paramedian", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Paramedian Bilateral vocal cord palsy is usually a congenital abductor paralysis . The vocal cords lie in the paramedian position with consequent inspiratory stridor .</p>\n<p><strong>Highyeild:</strong></p><p>B/L abductor paralysis: Both cords lie either in the median or in the paramedian position Voice is good Dyspnea/stridor: May not be present for months/years (worse on exertion or during an attack of acute laryngitis)</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Cadaveric position is seen in paralysis of recurrent and superior laryngeal nerves. Option: C. Median position is seen in RLN paralysis. Option: D. Gentle Abduction is seen in the paralysis of adductors.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment Emergency tracheostomy (if the patient in distress) If EMG shows complete denervation of the muscles then perform lateralisation of one of the vocal cords. Lateralization Procedure: Arytenoidectomy (Woodman’s procedure- Donnie’s procedure or LASER arytenoidectomy) Vocal cord lateralisation through endoscopes (Kirschner’s method) Cordectomy Nerve-muscle implant</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 27 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 3</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Vocal Nodules, Polyps, Laryngocele - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 3</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 3 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "Continuous positive airway pressure (CPAP) is effective for obstructive sleep apnea (OSA) because it:", "options": [{"label": "A", "text": "Supports the soft palate", "correct": false}, {"label": "B", "text": "Compresses the tongue base", "correct": false}, {"label": "C", "text": "Stimulates activity of dilating muscles", "correct": false}, {"label": "D", "text": "Prevents pharyngeal collapse during expiration", "correct": true}], "correct_answer": "D. Prevents pharyngeal collapse during expiration", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Prevents pharyngeal collapse during expiration CPAP prevents pharyngeal collapse during expiration, which is effective for obstructive sleep apnea.</p>\n<p><strong>Highyeild:</strong></p><p>The purpose of CPAP is to stent open the airway, which typically collapses during end-expiration in OSA patients. CPAP can stent open the collapsed segment during end-expiration. Continuous positive airway pressure (CPAP) provides a pneumatic splint to the airway and increases its calibre. The optimum airway pressure for a device to open the airway is determined during a sleep study and is usually kept at 5–20 cm H2O When CPAP is not tolerated, a BiPAP (bilevel positive airway pressure) device is used. It delivers positive pressure at two fixed levels—a higher inspiratory and a lower expiratory pressure. An auto-titrating PAP (APAP) is also available, continuously adjusting the pressure. Their disadvantages are the same as those of CPAP.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "True about vocal nodules is/are:", "options": [{"label": "A", "text": "Also known as screamer's node", "correct": false}, {"label": "B", "text": "Occur at the junction of anterior 1 /3rd and posterior 2/3rd of vocal cords", "correct": false}, {"label": "C", "text": "Most common presentation is hoarseness of voice", "correct": false}, {"label": "D", "text": "All of these are true", "correct": true}], "correct_answer": "D. All of these are true", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>All of these are true All the statements described above are true.</p>\n<p><strong>Highyeild:</strong></p><p>Vocal Nodule They appear symmetrically on the free edge of the vocal cord, at the junction of the anterior one-third, with the posterior two-thirds, as this is the area of maximum cord vibration and is thus subject to maximum trauma. They result from vocal trauma when a person speaks in unnatural low tones for prolonged periods or at high intensities. They mainly affect teachers, actors, vendors or pop singers. Pathologically, trauma to the vocal cord in the form of vocal abuse or misuse causes oedema and haemorrhage in the submucosal space. This undergoes hyalinisation and fibrosis. The overlying epithelium also undergoes hyperplasia forming a nodule. Patients with vocal nodules complain of hoarseness. Vocal fatigue and pain in the neck on prolonged phonation are other common symptoms. Vocal nodules.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options: - Option: A. Vocal nodules, also known as screamer's node, is a true statement. Option: B. Vocal nodules appear symmetrically on the free edge of the vocal cord, at the junction of the anterior one-third, with the posterior two-thirds, as this is the area of maximum vibration of the cord and is thus subject to maximum trauma. Option: C. Most common presentation is hoarseness of voice is also a true statement.</p>\n<p><strong>Extraedge:</strong></p><p>Early cases of vocal nodules can be treated conservatively by educating the patient on the proper use of voice. Surgery is required for large nodules or nodules of long-standing in adults. Speech therapy and re-education in voice production are essential to prevent their recurrence.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Most common location of vocal nodule:", "options": [{"label": "A", "text": "Anterior 1/3 and posterior 2/3 junction", "correct": true}, {"label": "B", "text": "Anterior Commissure", "correct": false}, {"label": "C", "text": "Posterior 1/3 and anterior 2/3 junction", "correct": false}, {"label": "D", "text": "Posterior Commissure", "correct": false}], "correct_answer": "A. Anterior 1/3 and posterior 2/3 junction", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Anterior 1/3 and posterior 2/3 junction Vocal nodules appear symmetrically on the free edge of the vocal cord, at the junction of the anterior one-third, with the posterior two-thirds , as this is the area of maximum cord vibration and is thus subject to maximum trauma.</p>\n<p><strong>Highyeild:</strong></p><p>Vocal Nodule They appear symmetrically on the free edge of the vocal cord, at the junction of the anterior one-third, with the posterior two-thirds, as this is the area of maximum cord vibration and is thus subject to maximum trauma. They result from vocal trauma when a person speaks in unnatural low tones for prolonged periods or at high intensities. They mainly affect teachers, actors, vendors or pop singers. Pathologically, trauma to the vocal cord in the form of vocal abuse or misuse causes oedema and haemorrhage in the submucosal space. This undergoes hyalinisation and fibrosis. The overlying epithelium also undergoes hyperplasia forming a nodule. Patients with vocal nodules complain of hoarseness. Vocal fatigue and pain in the neck on prolonged phonation are other common symptoms. Vocal nodules.</p>\n<p><strong>Extraedge:</strong></p><p>Early vocal nodules can be treated conservatively by educating the patient on the proper use of voice. Surgery is required for large nodules or nodules of long-standing in adults. Speech therapy and re-education in voice production are essential to prevent their recurrence.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 13 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">
Instructions
Test Features:
Multiple choice questions with single correct answers
Timer-based testing for realistic exam conditions
Mark questions for review functionality
Comprehensive results and performance analysis
Mobile-optimized interface for learning on-the-go
Start Test
<!-- Quiz Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="quiz"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <!-- Progress Bar --> <div class="w-full bg-gray-200 rounded-full h-3 mb-4"> <div class="progress-bar h-3 rounded-full" id="progress-bar" style="width: 0%"></div> </div> <!-- Question Header --> <div class="flex flex-col md:flex-row justify-between items-center mb-4"> <h2 class="text-lg font-semibold" id="question-number">Question <span>1</span> of 13</h2> <p class="text-lg font-semibold mt-2 md:mt-0" id="timer">Time Remaining: <span>00:00</span></p> </div> <!-- Question Content --> <div class="mb-6" id="question-content"> <p class="text-gray-800 mb-4" id="question-text"></p> <div class="flex flex-wrap gap-4 mb-4" id="question-images"></div> <div class="space-y-3" id="options"></div> </div> <!-- Navigation Buttons --> <div class="flex flex-col md:flex-row justify-between items-center gap-2 md:gap-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition w-full md:w-auto" disabled="" id="previous-btn">Previous</button> <button class="bg-yellow-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-yellow-400 transition w-full md:w-auto" id="mark-review">Mark for Review</button> <button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="next-btn">Next</button> <button class="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full md:w-auto" id="nav-toggle">Questions Navigation</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition w-full md:w-auto" id="submit-test">Submit Test</button> </div> </div> </section> <!-- Results Section --> <section class="container mx-auto p-4 md:p-6 hidden section-transition" id="results"> <div class="bg-white rounded-lg shadow-md p-4 md:p-6"> <h2 class="text-2xl font-semibold mb-4">Voice Disorders - Results</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <p><strong>Correct:</strong> <span id="correct-count">0</span></p> <p><strong>Wrong:</strong> <span id="wrong-count">0</span></p> <p><strong>Unanswered:</strong> <span id="unanswered-count">0</span></p> <p><strong>Marked for Review:</strong> <span id="marked-count">0</span></p> </div> <h3 class="text-lg font-semibold mb-4" id="result-question-number">Question <span>1</span> of 13</h3> <div class="space-y-6" id="results-content"></div> <div class="result-nav"> <button aria-label="Previous question result" class="result-nav-btn" disabled="" id="prev-result">Previous</button> <button aria-label="Toggle results navigation panel" class="result-nav-btn" id="results-nav-toggle">Results Navigation</button> <button aria-label="Next question result" class="result-nav-btn" id="next-result">Next</button> </div> <div class="mt-6 flex space-x-4 button-group md:flex-row flex-col"> <button class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 transition" id="take-again">Take Again</button> <button class="bg-gray-500 text-white px-6 py-2 rounded-lg hover:bg-gray-600 transition" id="review-test">Review Test</button> </div> </div> </section> <!-- Exit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="exit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Leave Test?</h2> <p class="text-gray-700 mb-4">Your progress will be lost if you leave this page. Are you sure you want to exit?</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="continue-test">No, Continue</button> <button class="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition" id="exit-test">Yes, Exit</button> </div> </div> </div> <!-- Submit Confirmation Modal --> <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden" id="submit-modal"> <div class="bg-white rounded-lg p-6 max-w-sm w-full"> <h2 class="text-xl font-semibold mb-4">Confirm Submission</h2> <p class="text-gray-700 mb-2">You have attempted <span id="attempted-count">0</span> of 13 questions.</p> <p class="text-gray-700 mb-4"><span id="unattempted-count">0</span> questions are unattempted.</p> <div class="flex justify-end space-x-4"> <button class="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition" id="cancel-submit">Cancel</button> <button class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition" id="confirm-submit">Submit Test</button> </div> </div> </div> <!-- Quiz Navigation Panel --> <div class="fixed inset-y-0 right-0 nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="nav-panel"> <h2 class="text-lg font-semibold mb-4">Questions Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-nav">Close</button> </div> <!-- Results Navigation Panel --> <div class="fixed inset-y-0 right-0 results-nav-panel bg-white shadow-lg p-4 hidden overflow-y-auto" id="results-nav-panel"> <h2 class="text-lg font-semibold mb-4">Results Navigation</h2> <div class="mb-4"> <select class="w-full p-2 border rounded-lg text-gray-700" id="results-nav-filter"> <option value="all">All Questions</option> <option value="answered">Answered</option> <option value="unanswered">Unanswered</option> <option value="marked">Marked for Review</option> </select> </div> <div class="grid grid-cols-5 gap-2 md:gap-3" id="results-nav-grid"></div> <button class="mt-4 bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition w-full" id="close-results-nav">Close</button> </div> <!-- JavaScript Logic --> <script> // Enable debug mode for detailed logging const DEBUG_MODE = true; // Log debug messages function debugLog(message) { if (DEBUG_MODE) { console.log(`[DEBUG] ${message}`); } } // Initialize questions with error handling let questions = []; let currentResultQuestion = 0; // State for current question in results try { debugLog("Attempting to parse questions_json"); questions = [{"text": "This condition would lead to the following type of speech pathology:", "options": [{"label": "A", "text": "Rhinolalia Clausa", "correct": false}, {"label": "B", "text": "Rhinolalia Aperta", "correct": true}, {"label": "C", "text": "Puberphonia", "correct": false}, {"label": "D", "text": "None of the above", "correct": false}], "correct_answer": "B. Rhinolalia Aperta", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003445119-QTDE084001IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rhinolalia Aperta Cleft lip/palate gives rise to rhinolalia aperta/hypernasality. Words with little nasal resonance are resonated through the nose. The defect is due to failure of the nasopharynx to cut off from the oropharynx or abnormal communication between the oral and nasal cavities.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Rhinolalia Clausa lacks nasal resonance for words resonated in the nasal cavity, e.g. m, n, ng. It is due to blockage of the nose or nasopharynx. Option: C. Normally, childhood voice has a higher pitch. When the larynx matures at puberty, vocal cords lengthen, and the voice changes to a lower pitch. This is a feature exclusive to males. Failure of this change leads to the persistence of high-pitched childhood voices and is called puberphonia.</p>\n<p><strong>Extraedge:</strong></p><p>STUTTERING It is a disorder of fluency of speech and consists of hesitation to start, repetitions, prolongations or blocks in the flow of speech. When well-established, a stutterer may develop secondary mannerisms such as facial grimacing, eye blink and abnormal head movements.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Puberphonia can be corrected by doing.", "options": [{"label": "A", "text": "Type I Thyroplasty", "correct": false}, {"label": "B", "text": "Type II Thyroplasty", "correct": false}, {"label": "C", "text": "Type III Thyroplasty", "correct": true}, {"label": "D", "text": "Type IV Thyroplasty", "correct": false}], "correct_answer": "C. Type III Thyroplasty", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Type III Thyroplasty Puberphonia is also known as mutational falsetto or voice break . Ischii type III relaxation thyroplasty has shown promise in managing these patients.</p>\n<p><strong>Highyeild:</strong></p><p>TYPES OF THYROPLASTY Type I is the vocal cord's medial displacement, as achieved in Teflon paste injection. Type II is lateral displacement of the vocal cord and is used to improve the airway. Type III is used to shorten (relax) the vocal cord. Relaxation of the vocal cord lowers the pitch. This procedure is done in mutational falsetto or in those who have undergone gender transformation from female to male. Type IV - This procedure is used to lengthen (tighten) the vocal cord and elevate the pitch. It converts male voice characters to females and has been used in gender transformation. It is also used when the vocal cord is lax and bowing due to ageing or trauma.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption: A. Type I thyroplasty is the medial displacement of the vocal cord, as is achieved in Teflon paste injection. O ption: B. Type II thyroplasty is lateral displacement of the vocal cord and is used to improve the airway. O ption: D. Type IV thyroplasty lengthens (tightens) the vocal cord and elevates the pitch. It converts male voice characters to females and has been used in gender transformation. It is also used when the vocal cord is lax and bowing due to an ageing process or trauma.</p>\n<p><strong>Extraedge:</strong></p><p>PUBERPHONIA Normally, childhood voice has a higher pitch. When the larynx matures at puberty, vocal cords lengthen and the voice changes to one of lower pitch. This is a feature exclusive to males. Failure of this change leads to persistence of childhood high-pitched voice and is called puberphonia. It is seen in boys who are emotionally immature, feel insecure and show excessive fixation to their mother.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Gold standard test for laryngopharyngeal reflux is?", "options": [{"label": "A", "text": "Esophageal Motility Study", "correct": false}, {"label": "B", "text": "Barium Swallow", "correct": false}, {"label": "C", "text": "24-hour double probe pH monitoring", "correct": true}, {"label": "D", "text": "Esophageal Biopsy", "correct": false}], "correct_answer": "C. 24-hour double probe pH monitoring", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>24-hour double probe pH monitoring Laryngopharyngeal reflux (LPR) is the backflow of gastric contents into the pharynx and larynx. The gold standard for diagnosis is dual-probe 24-hour pH testing with the upper probe positioned above the upper oesophageal sphincter.</p>\n<p><strong>Extraedge:</strong></p><p>Treatment may require three months or more of twice-daily proton pump inhibitors and lifestyle modifications</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In a patient with hypertrophied adenoids, the voice abnormality that is seen is:", "options": [{"label": "A", "text": "Rhinolalia Clausa", "correct": true}, {"label": "B", "text": "Rhinolalia Aperta", "correct": false}, {"label": "C", "text": "Hot potato voice", "correct": false}, {"label": "D", "text": "Staccato Voice", "correct": false}], "correct_answer": "A. Rhinolalia Clausa", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Rhinolalia Clausa Rhinolalia clausa is a lack of nasal resonance (hyponasality). It is seen in conditions that block the nose or nasopharynx. So will be seen in allergic rhinitis, adenoids and nasal polyps.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Rhinolalia Aperta is seen when certain words with little nasal resonance are resonated through the The defect is in failure of the nasopharynx to cut off from the oropharynx or abnormal communication between the oral and nasal cavities. Option: C. Hot potato voice is seen in peritonsillar abscee/quinsy. Option: D. Staccato Voice is abnormal speech with pauses between words, sometimes associated with multiple sclerosis.</p>\n<p><strong>Extraedge:</strong></p><p>STUTTERING It is a disorder of fluency of speech and consists of hesitation to start, repetitions, prolongations or blocks in the flow of speech. When well-established, a stutterer may develop secondary mannerisms such as facial grimacing, eye blink and abnormal head movements.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Study the given Image and choose the most inappropriate statement:", "options": [{"label": "A", "text": "Usually caused by vocal abuse", "correct": false}, {"label": "B", "text": "There is a collection of oedema fluid in the subepithelial space", "correct": false}, {"label": "C", "text": "There is asymmetrical swelling of vocal cords", "correct": true}, {"label": "D", "text": "Vocal cord stripping is the treatment", "correct": false}], "correct_answer": "C. There is asymmetrical swelling of vocal cords", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003447985-QTDE084009IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>There is asymmetrical swelling of vocal cords The given Image shows Reinke's oedema . It is bilateral symmetrical swelling of the whole of the membranous part of the vocal cords, most often seen in middle-aged men and women. So option C is not correct.</p>\n<p><strong>Highyeild:</strong></p><p>REINKE’S EDEMA It is bilateral symmetrical swelling of the whole of the membranous part of the vocal cords, most often seen in middle-aged men and women. This is due to oedema of the vocal cords' subepithelial space (Reinke’s space).</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Usually caused by vocal abuse is a true statement. Option: B. A collection of oedema fluid in the subepithelial space is also a true statement. Option: D. Vocal cord stripping is the treatment is also a true statement.</p>\n<p><strong>Extraedge:</strong></p><p>Chronic irritation of vocal cords due to misuse of the voice, heavy smoking, chronic sinusitis and laryngopharyngeal reflex is the probable etiological factors.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 25-year-old male desires to undergo gender transformation to female. Which of the following phono surgeries will help him achieve the desired result?", "options": [{"label": "A", "text": "Injection of vocal cord with Teflon paste", "correct": false}, {"label": "B", "text": "Laryngeal Reinnervation Procedures", "correct": false}, {"label": "C", "text": "Thyroplasty Type 3", "correct": false}, {"label": "D", "text": "Thyroplasty Type 4", "correct": true}], "correct_answer": "D. Thyroplasty Type 4", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Thyroplasty Type 4 Thyroplasty type 4 lengthens or tightens the vocal cord and elevates the pitch. It converts the male character of voice to female and is used in gender transformation.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption:A. Injection with Teflon paste is used to augment and medialise the paralysed vocal cord. O ption: B. Reinnervation is used to innervate the paralysed thyroarytenoid muscle. O ption:C. Thyroplasty type 3 is used to shorten or relax the vocal cord, lowering the pitch. It is done in mutational falsetto or in those who have undergone gender transformation from female to male.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "What is the probable diagnosis?", "options": [{"label": "A", "text": "Laryngocele", "correct": true}, {"label": "B", "text": "Ductal CYST", "correct": false}, {"label": "C", "text": "Saccular Cyst", "correct": false}, {"label": "D", "text": "Chondroma", "correct": false}], "correct_answer": "A. Laryngocele", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003448430-QTDE084012IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Laryngocele The patient is performing the Valsalva manoeuvre. Here patient attempts to blow through the mouth with the nose closed. An external laryngocele presents as a reducible swelling in the neck which increases in size on coughing or performing Valsalva.</p>\n<p><strong>Highyeild:</strong></p><p>LARYNGOCELE A laryngocele is supposed to arise from raised trans glottic air pressure, as in trumpet players, glass-blowers or weight lifters.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption: B. Ductal cysts are seen in vallecular, aryepiglottic folds, false cords, ventricles and pyriform fossa. O ption: C. Saccular cyst presents in the laryngeal ventricle. O ption: D. Chondroma present in subglottic area.</p>\n<p><strong>Extraedge:</strong></p><p>Types of larngocele Internal is confined within the larynx and presents as a false cord and aryepiglottic fold distension. External in which distended saccule herniates through the thyroid membrane and is present in the neck. Combined or mixed in which, both internal and external components are seen. Treatment is surgical excision through an external neck incision.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "In which of the following sites is sound produced by this device?", "options": [{"label": "A", "text": "Oesophagus", "correct": false}, {"label": "B", "text": "Hypopharynx", "correct": true}, {"label": "C", "text": "Back of the oral cavity", "correct": false}, {"label": "D", "text": "Trachea", "correct": false}], "correct_answer": "B. Hypopharynx", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003448914-QTDE084013IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Hypopharynx The given device is the It is a transistorised, battery-operated portable device . Its vibrating disc is held against the neck's soft tissues, and a low-pitched sound is produced in the hypopharynx, which is further modulated into speech by the tongue, lips, teeth and palate.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. In the esophageal speech, the sound is produced by swallowing air, holding it in the upper esophagus, and slowly releasing it into the pharynx. Option: C. In a transoral pneumatic device, vibrations produced in a rubber diaphragm are carried by a plastic tube into the back of the oral cavity, where modulators convert sound into speech. Option: D. In trachea–oesophageal speech, the air is carried from the trachea to the oesophagus or hypopharynx by creating skin lined fistula or placing a prosthesis.</p>\n<p><strong>Extraedge:</strong></p><p>Transoral pneumatic device Another type of artificial larynx is a transoral device. Here vibrations produced in a rubber diaphragm are carried by a plastic tube into the back of the oral cavity where sound is converted into speech by modulators. This is a pneumatic type of device and uses expired air from the tracheostome to vibrate the diaphragm.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Young man whose voice has not broken is called?", "options": [{"label": "A", "text": "Puberphonia", "correct": true}, {"label": "B", "text": "Phonaesthenia", "correct": false}, {"label": "C", "text": "Plica Ventricularis", "correct": false}, {"label": "D", "text": "Functional Aphonia", "correct": false}], "correct_answer": "A. Puberphonia", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Puberphonia In males, at puberty , the voice drops typically by an octave and becomes low pitch. It occurs because vocal cords lengthen . Failure of this change leads to the persistence of high-pitched childhood voice and is called as puberphonia</p>\n<p><strong>Highyeild:</strong></p><p>PUBERPHONIA It is seen in boys who are emotionally insecure and show excessive attachment to their mothers. Their physical and sexual development is normal. This condition is also called falsetto mutation. Treatment Training the body to produce low pitched voice.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: B. Phonaesthenia is the weakness of voice due to fatigue of phonatory muscles. Thyroarytenoids and inter arytenoids or both may be affected. It is seen in abuse or misuse of voice or following laryngitis . Option: C. In Dysphonia plica, ventricularis voice is produced by ventricular folds (false cords) that have taken over the true cords' function. The voice is rough, low-pitched and unpleasant. Option: D. Functional Aphonia is a functional disorder primarily seen in emotionally labile females aged 15–30 years.</p>\n<p><strong>Extraedge:</strong></p><p>Gutzmann pressure test In this test, thyroid prominence is pressed backwards and downward, producing a low-tone voice. If this test is positive, it indicates puberphonia.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Keyhole appearance is seen in:", "options": [{"label": "A", "text": "Functional Aphonia", "correct": false}, {"label": "B", "text": "Puberphonia", "correct": false}, {"label": "C", "text": "Phonasthenia", "correct": true}, {"label": "D", "text": "Vocal cord paralysis", "correct": false}], "correct_answer": "C. Phonasthenia", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Phonasthenia Keyhole appearance is seen in phonasthenia.</p>\n<p><strong>Highyeild:</strong></p><p>Phonasthenia is the weakness of voice due to fatigue of phonatory muscles, either thyroarytenoid, inter arytenoid or both.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Option: A. Functional Aphonia is a functional disorder primarily seen in emotionally labile females aged 15–30 years. Option: B. Normally, childhood voice has a higher pitch. When the larynx matures at puberty, vocal cords lengthen, and the voice changes to a lower pitch. This is a feature exclusive to males. Failure of this change leads to the persistence of high-pitched childhood voices and is called puberphonia. Option: D. In Vocal cord paralysis, keyhole appearance is not seen.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "Patient Complaints of hoarseness, Vocal fatigue and pain in the neck on prolonged phonation. Diagnosis is:", "options": [{"label": "A", "text": "Reinke’S Oedema", "correct": false}, {"label": "B", "text": "Contact Ulcer", "correct": false}, {"label": "C", "text": "Vocal Polyp", "correct": false}, {"label": "D", "text": "Vocal Nodules", "correct": true}], "correct_answer": "D. Vocal Nodules", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003449592-QTDE084016IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Vocal Nodules The above image and history describe the vocal/ singer’s nodules.</p>\n<p><strong>Highyeild:</strong></p><p>VOCAL NODULES/SINGER’S NODULES They appear symmetrically on the free edge of the vocal cord, at the junction of the anterior one-third, with the posterior two-thirds, as this is the area of maximum cord vibration and is thus subject to maximum trauma. They mainly affect teachers, actors, vendors or pop singers. Speech therapy and re-education in voice production are essential to prevent their recurrence. Patients with vocal nodules complain of hoarseness. Vocal fatigue and pain in the neck on prolonged phonation are other common symptoms. Vocal nodules.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption: A. Under the epithelium of vocal cords is a potential space with scanty subepithelial connective tissues. It is bounded above and below by the arcuate lines, in front by the anterior commissure, and behind by the vocal process of the arytenoid. Oedema of this space causes fusiform swelling of the membranous cords (Reinke’s oedema). O ption: B. Contact ulcer is again due to faulty voice production in which vo- cal processes of arytenoids hammer against each other, resulting in ulceration and granuloma formation. Some cases are due to gastric reflux. O ption: C. Vocal polyp is the result of vocal abuse or misuse. Other contributing factors are allergies and smoking. Primarily, it affects men in the age group of 30–50 years. Typically, a vocal polyp is unilateral, arising from the same position as a vocal nodule. It is soft, smooth and often pedunculated.</p>\n<p><strong>Extraedge:</strong></p><p>They mainly affect teachers, actors, vendors or pop singers. Speech therapy and re-education in voice production are essential to prevent their recurrence.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 25-year-old female singer by profession complained of a change of voice. Direct laryngoscopic examination finding is shown below. What is the treatment of choice for large nodules?", "options": [{"label": "A", "text": "Micro Laryngeal Surgery", "correct": true}, {"label": "B", "text": "Proton Pump Inhibitors", "correct": false}, {"label": "C", "text": "Anti-Allergic Medications", "correct": false}, {"label": "D", "text": "Observation", "correct": false}], "correct_answer": "A. Micro Laryngeal Surgery", "question_images": ["https://dbmi-data.s3.ap-south-1.amazonaws.com/photos-1685003449694-QTDE084017IMG1.JPG"], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Micro Laryngeal Surgery The given Image and history tell us that the diagnosis is a Vocal nodule, also known as Singer’s nodules . Amongst the given options, the treatment for vocal nodules is Micro laryngeal surgery.</p>\n<p><strong>Highyeild:</strong></p><p>VOCAL NODULES/SINGER’S NODULES They appear symmetrically on the free edge of the vocal cord, at the junction of the anterior one-third, with the posterior two-thirds, as this is the area of maximum cord vibration and is thus subject to maximum trauma. They mainly affect teachers, actors, vendors or pop singers. Speech therapy and re-education in voice production are essential to prevent their recurrence. Vocal nodules.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- O ption: B. Proton Pump Inhibitors have no role in large nodules. O ption: C. Anti-Allergic Medications have no role in large nodules. O ption: D. Observation also has no role in treatment.</p>\n<p><strong>Extraedge:</strong></p><p>They mainly affect teachers, actors, vendors or pop singers. Speech therapy and re-education in voice production are essential to prevent their recurrence.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}, {"text": "A 10-year-old boy developed hoarseness of voice following an attack of diphtheria. There is no other significant history. On examination, his right vocal cord was paralysed. The best treatment of choice for the paralysed vocal cord will be:", "options": [{"label": "A", "text": "Gelfoam injection of the right vocal cord", "correct": false}, {"label": "B", "text": "Thyroplasty Type 1", "correct": false}, {"label": "C", "text": "Wait for spontaneous recovery of vocal cord", "correct": true}, {"label": "D", "text": "Fat injection of right vocal cord", "correct": false}], "correct_answer": "C. Wait for spontaneous recovery of vocal cord", "question_images": [], "explanation_images": [], "explanation": "<p><strong>Solution:</strong></p><p>Wait for spontaneous recovery of vocal cord Since the patient here has only hoarseness, no strider, no aspiration, and it is a unilateral recurrent laryngeal Nerve palsy where the vocal cord is in the paramedian position, conservative management must be followed.</p>\n<p><strong>Random:</strong></p><p>Explanation for incorrect options:- Options A, B & D. The other options are vocal cord medicalisation required in complete unilateral palsy where the voices breathe the forest whisper type, and there is a predisposition to aspiration.</p>\n<p>@dams_new_robot</p>", "bot": "@dams_new_robot", "video": ""}]; if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions data is empty or invalid"); } debugLog(`Successfully parsed ${questions.length} questions`); } catch (e) { console.error("Failed to parse questions_json:", e); document.getElementById('error-message').innerHTML = "Error loading quiz data. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; // Fallback to sample questions for testing questions = [ { text: "What is 2 + 2?", options: [ { label: "A", text: "3", correct: false }, { label: "B", text: "4", correct: true }, { label: "C", text: "5", correct: false }, { label: "D", text: "6", correct: false } ], correct_answer: "B. 4", question_images: [], explanation_images: [], explanation: "<p>2 + 2 = 4</p><p>@dams_new_robot</p>", bot: "@dams_new_robot", audio: "", video: "" } ]; debugLog("Loaded fallback questions"); } // Quiz state let currentQuestion = 0; let answers = new Array(questions.length).fill(null); let markedForReview = new Array(questions.length).fill(false); let timeRemaining = 23 * 60; // Duration in seconds let timerInterval = null; const quizId = `{title.replace(/\s+/g, '_').toLowerCase()}`; // Unique ID for local storage // Load saved progress function loadProgress() { try { debugLog("Loading progress from localStorage"); const saved = localStorage.getItem(`quiz_${quizId}`); if (saved) { const { savedAnswers, savedMarked, savedTime } = JSON.parse(saved); answers = savedAnswers || answers; markedForReview = savedMarked || markedForReview; timeRemaining = savedTime !== undefined ? savedTime : timeRemaining; debugLog("Progress loaded successfully"); } else { debugLog("No saved progress found"); } } catch (e) { console.error("Error loading progress:", e); debugLog("Failed to load progress: " + e.message); } } // Save progress function saveProgress() { try { debugLog("Saving progress to localStorage"); localStorage.setItem(`quiz_${quizId}`, JSON.stringify({ savedAnswers: answers, savedMarked: markedForReview, savedTime: timeRemaining })); debugLog("Progress saved successfully"); } catch (e) { console.error("Error saving progress:", e); debugLog("Failed to save progress: " + e.message); } } // Initialize quiz function initQuiz() { try { debugLog("Initializing quiz"); loadProgress(); const startButton = document.getElementById('start-test'); if (!startButton) { throw new Error("Start test button not found"); } startButton.addEventListener('click', startQuiz); debugLog("Start test button listener attached"); document.getElementById('previous-btn').addEventListener('click', showPreviousQuestion); document.getElementById('next-btn').addEventListener('click', showNextQuestion); document.getElementById('mark-review').addEventListener('click', toggleMarkForReview); document.getElementById('nav-toggle').addEventListener('click', toggleNavPanel); document.getElementById('submit-test').addEventListener('click', showSubmitModal); document.getElementById('continue-test').addEventListener('click', closeExitModal); document.getElementById('exit-test').addEventListener('click', () => { debugLog("Exiting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('cancel-submit').addEventListener('click', closeSubmitModal); document.getElementById('confirm-submit').addEventListener('click', submitTest); document.getElementById('take-again').addEventListener('click', () => { debugLog("Restarting test"); localStorage.removeItem(`quiz_${quizId}`); window.location.reload(); }); document.getElementById('review-test').addEventListener('click', () => showResults(currentResultQuestion)); document.getElementById('close-nav').addEventListener('click', toggleNavPanel); document.getElementById('theme-toggle').addEventListener('click', toggleTheme); document.getElementById('nav-filter').addEventListener('change', updateNavPanel); document.getElementById('prev-result').addEventListener('click', showPreviousResult); document.getElementById('next-result').addEventListener('click', showNextResult); document.getElementById('results-nav-toggle').addEventListener('click', toggleResultsNavPanel); document.getElementById('close-results-nav').addEventListener('click', toggleResultsNavPanel); document.getElementById('results-nav-filter').addEventListener('change', updateResultsNavPanel); debugLog("Quiz initialized successfully"); } catch (e) { console.error("Failed to initialize quiz:", e); debugLog("Failed to initialize quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('start-test').disabled = true; } } // Start quiz function startQuiz() { try { debugLog("Starting quiz"); document.getElementById('instructions').classList.add('hidden'); document.getElementById('quiz').classList.remove('hidden'); showQuestion(currentQuestion); startTimer(); updateNavPanel(); debugLog("Quiz started successfully"); } catch (e) { console.error("Error starting quiz:", e); debugLog("Failed to start quiz: " + e.message); document.getElementById('error-message').innerHTML = "Error starting quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); document.getElementById('quiz').classList.add('hidden'); document.getElementById('instructions').classList.remove('hidden'); } } // Show question function showQuestion(index) { try { debugLog(`Showing question ${index + 1}`); currentQuestion = index; const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } document.getElementById('question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('question-text').innerHTML = q.text || "No question text available"; const imagesDiv = document.getElementById('question-images'); imagesDiv.innerHTML = q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg">`).join('') : ''; const optionsDiv = document.getElementById('options'); optionsDiv.innerHTML = q.options && q.options.length > 0 ? q.options.map(opt => ` <button class="option-btn w-full text-left p-3 border rounded-lg ${answers[index] === opt.label ? 'selected' : ''}" onclick="selectOption(${index}, '${opt.label}')" aria-label="Option ${opt.label}: ${opt.text}"> ${opt.label}. ${opt.text} </button> `).join('') : '<p class="text-red-500">No options available</p>'; document.getElementById('previous-btn').disabled = index === 0; document.getElementById('next-btn').disabled = index === questions.length - 1; document.getElementById('mark-review').classList.toggle('marked', markedForReview[index]); updateProgressBar(); saveProgress(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying question:", e); debugLog("Failed to display question: " + e.message); } } // Select option function selectOption(index, label) { try { debugLog(`Selecting option ${label} for question ${index + 1}`); answers[index] = label; const optionsDiv = document.getElementById('options'); const optionButtons = optionsDiv.querySelectorAll('.option-btn'); optionButtons.forEach(btn => { const btnLabel = btn.textContent.trim().split('.')[0]; btn.classList.toggle('selected', btnLabel === label); }); updateNavPanel(); saveProgress(); debugLog(`Option ${label} selected for question ${index + 1}`); } catch (e) { console.error("Error selecting option:", e); debugLog("Failed to select option: " + e.message); } } // Toggle mark for review function toggleMarkForReview() { try { debugLog(`Toggling mark for review on question ${currentQuestion + 1}`); markedForReview[currentQuestion] = !markedForReview[currentQuestion]; document.getElementById('mark-review').classList.toggle('marked', markedForReview[currentQuestion]); updateNavPanel(); saveProgress(); debugLog(`Mark for review toggled for question ${currentQuestion + 1}`); } catch (e) { console.error("Error marking for review:", e); debugLog("Failed to mark for review: " + e.message); } } // Navigate to previous question function showPreviousQuestion() { try { debugLog(`Navigating to previous question from ${currentQuestion + 1}`); if (currentQuestion > 0) { currentQuestion--; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to previous question:", e); debugLog("Failed to navigate to previous question: " + e.message); } } // Navigate to next question function showNextQuestion() { try { debugLog(`Navigating to next question from ${currentQuestion + 1}`); if (currentQuestion < questions.length - 1) { currentQuestion++; showQuestion(currentQuestion); } } catch (e) { console.error("Error navigating to next question:", e); debugLog("Failed to navigate to next question: " + e.message); } } // Handle question navigation click function handleQuestionNavClick(index) { try { debugLog(`Navigating to question ${index + 1} via nav panel`); showQuestion(index); toggleNavPanel(); } catch (e) { console.error("Error handling navigation click:", e); debugLog("Failed to navigate via nav panel: " + e.message); } } // Start timer function startTimer() { try { debugLog("Starting timer"); timerInterval = setInterval(() => { if (timeRemaining <= 0) { debugLog("Timer expired, submitting test"); clearInterval(timerInterval); submitTest(); } else { timeRemaining--; const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; document.getElementById('timer').innerHTML = `Time Remaining: <span>${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}</span>`; saveProgress(); } }, 1000); debugLog("Timer started successfully"); } catch (e) { console.error("Error starting timer:", e); debugLog("Failed to start timer: " + e.message); } } // Update progress bar function updateProgressBar() { try { debugLog("Updating progress bar"); const progress = ((currentQuestion + 1) / questions.length) * 100; document.getElementById('progress-bar').style.width = `${progress}%`; debugLog("Progress bar updated"); } catch (e) { console.error("Error updating progress bar:", e); debugLog("Failed to update progress bar: " + e.message); } } // Update quiz navigation panel function updateNavPanel() { try { debugLog("Updating quiz navigation panel"); const filter = document.getElementById('nav-filter').value; const navGrid = document.getElementById('nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="question-nav-btn ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleQuestionNavClick(${i})" aria-label="Go to Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Quiz navigation panel updated"); } catch (e) { console.error("Error updating quiz navigation panel:", e); debugLog("Failed to update quiz navigation panel: " + e.message); } } // Update results navigation panel function updateResultsNavPanel() { try { debugLog("Updating results navigation panel"); const filter = document.getElementById('results-nav-filter').value; const navGrid = document.getElementById('results-nav-grid'); navGrid.innerHTML = questions.map((_, i) => { if (filter === 'answered' && !answers[i]) return ''; if (filter === 'unanswered' && answers[i]) return ''; if (filter === 'marked' && !markedForReview[i]) return ''; return ` <button class="result-nav-btn-grid ${answers[i] ? 'answered' : 'unanswered'} ${markedForReview[i] ? 'marked-nav' : ''}" onclick="handleResultNavClick(${i})" aria-label="Go to Result for Question ${i + 1}"> ${i + 1} </button> `; }).join(''); debugLog("Results navigation panel updated"); } catch (e) { console.error("Error updating results navigation panel:", e); debugLog("Failed to update results navigation panel: " + e.message); } } // Toggle quiz navigation panel function toggleNavPanel() { try { debugLog("Toggling quiz navigation panel"); const navPanel = document.getElementById('nav-panel'); navPanel.classList.toggle('hidden'); debugLog("Quiz navigation panel toggled"); } catch (e) { console.error("Error toggling quiz navigation panel:", e); debugLog("Failed to toggle quiz navigation panel: " + e.message); } } // Toggle results navigation panel function toggleResultsNavPanel() { try { debugLog("Toggling results navigation panel"); const resultsNavPanel = document.getElementById('results-nav-panel'); resultsNavPanel.classList.toggle('hidden'); if (!resultsNavPanel.classList.contains('hidden')) { updateResultsNavPanel(); } debugLog("Results navigation panel toggled"); } catch (e) { console.error("Error toggling results navigation panel:", e); debugLog("Failed to toggle results navigation panel: " + e.message); } } // Handle result navigation click function handleResultNavClick(index) { try { debugLog(`Navigating to result for question ${index + 1} via nav panel`); showResults(index); toggleResultsNavPanel(); } catch (e) { console.error("Error handling result navigation click:", e); debugLog("Failed to navigate to result: " + e.message); } } // Show submit modal function showSubmitModal() { try { debugLog("Showing submit modal"); const attempted = answers.filter(a => a !== null).length; document.getElementById('attempted-count').textContent = attempted; document.getElementById('unattempted-count').textContent = questions.length - attempted; document.getElementById('submit-modal').classList.remove('hidden'); debugLog("Submit modal displayed"); } catch (e) { console.error("Error showing submit modal:", e); debugLog("Failed to show submit modal: " + e.message); } } // Close submit modal function closeSubmitModal() { try { debugLog("Closing submit modal"); document.getElementById('submit-modal').classList.add('hidden'); debugLog("Submit modal closed"); } catch (e) { console.error("Error closing submit modal:", e); debugLog("Failed to close submit modal: " + e.message); } } // Close exit modal function closeExitModal() { try { debugLog("Closing exit modal"); document.getElementById('exit-modal').classList.add('hidden'); debugLog("Exit modal closed"); } catch (e) { console.error("Error closing exit modal:", e); debugLog("Failed to close exit modal: " + e.message); } } // Submit test function submitTest() { try { debugLog("Submitting test"); clearInterval(timerInterval); document.getElementById('quiz').classList.add('hidden'); document.getElementById('submit-modal').classList.add('hidden'); document.getElementById('results').classList.remove('hidden'); showResults(0); // Start with first question // Trigger confetti animation confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }); localStorage.removeItem(`quiz_${quizId}`); debugLog("Test submitted successfully"); } catch (e) { console.error("Error submitting test:", e); debugLog("Failed to submit test: " + e.message); } } // Show result for a single question function showResults(index) { try { debugLog(`Showing result for question ${index + 1}`); currentResultQuestion = index; let correct = 0, wrong = 0, unanswered = 0, marked = 0; answers.forEach((answer, i) => { const isCorrect = answer && questions[i].options.find(opt => opt.label === answer)?.correct; if (answer === null) unanswered++; else if (isCorrect) correct++; else wrong++; if (markedForReview[i]) marked++; }); const q = questions[index]; if (!q) { throw new Error(`Question ${index} is undefined`); } const userAnswer = answers[index]; const isCorrect = userAnswer && q.options.find(opt => opt.label === userAnswer)?.correct; const resultsContent = document.getElementById('results-content'); resultsContent.innerHTML = ` <div class="border p-4 rounded-lg ${isCorrect ? 'bg-green-50' : userAnswer ? 'bg-red-50' : 'bg-gray-50'}"> <p class="font-semibold">Question ${index + 1}: ${q.text || 'No question text'}</p> ${q.question_images && q.question_images.length > 0 ? q.question_images.map(url => `<img src="${url}" alt="Question Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} <p><strong>Your Answer:</strong> ${userAnswer ? `${userAnswer}. ${q.options.find(opt => opt.label === userAnswer)?.text || 'Invalid option'}` : 'Unanswered'}</p> <p><strong>Correct Answer:</strong> ${q.correct_answer || 'Unknown'}</p> <div class="mt-2">${q.explanation || 'No explanation available'}</div> ${q.explanation_images && q.explanation_images.length > 0 ? q.explanation_images.map(url => `<img src="${url}" alt="Explanation Image" class="max-w-full h-auto rounded-lg my-2">`).join('') : ''} ${q.video ? ` <button class="play-video bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadVideo(this, '${q.video}', 'video-${index}')" aria-label="Play explanation video for Question ${index + 1}"> Play Video Explanation </button> <div id="video-${index}" class="video-container mt-2"></div> ` : '<p class="text-gray-500 mt-2">No video available</p>'} ${q.audio ? ` <button class="play-audio bg-blue-500 text-white px-4 py-2 rounded-lg mt-2" onclick="loadAudio(this, '${q.audio}', 'audio-${index}')" aria-label="Play audio explanation for Question ${index + 1}"> Play Audio Explanation </button> <div id="audio-${index}" class="audio-container mt-2"></div> ` : ''} </div> `; document.getElementById('correct-count').textContent = correct; document.getElementById('wrong-count').textContent = wrong; document.getElementById('unanswered-count').textContent = unanswered; document.getElementById('marked-count').textContent = marked; document.getElementById('result-question-number').innerHTML = `Question <span>${index + 1}</span> of ${questions.length}`; document.getElementById('prev-result').disabled = index === 0; document.getElementById('next-result').disabled = index === questions.length - 1; updateResultsNavPanel(); window.scrollTo({ top: 0, behavior: 'smooth' }); debugLog(`Result for question ${index + 1} displayed successfully`); } catch (e) { console.error("Error displaying result:", e); debugLog("Failed to display result: " + e.message); } } // Navigate to previous result function showPreviousResult() { try { debugLog(`Navigating to previous result from question ${currentResultQuestion + 1}`); if (currentResultQuestion > 0) { showResults(currentResultQuestion - 1); } } catch (e) { console.error("Error navigating to previous result:", e); debugLog("Failed to navigate to previous result: " + e.message); } } // Navigate to next result function showNextResult() { try { debugLog(`Navigating to next result from question ${currentResultQuestion + 1}`); if (currentResultQuestion < questions.length - 1) { showResults(currentResultQuestion + 1); } } catch (e) { console.error("Error navigating to next result:", e); debugLog("Failed to navigate to next result: " + e.message); } } // Lazy-load video function loadVideo(button, videoUrl, containerId) { try { debugLog(`Loading video for ${containerId}: ${videoUrl}`); if (!videoUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No video available</p>`; button.remove(); debugLog("No video URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <div class="video-loading"></div> <video controls class="w-full max-w-[600px] rounded-lg" preload="metadata" aria-label="Video explanation"> <source src="${videoUrl}" type="${videoUrl.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'}"> Your browser does not support the video tag. </video> `; container.classList.add('active'); button.remove(); // Initialize HLS.js for .m3u8 videos const video = container.querySelector('video'); if (videoUrl.endsWith('.m3u8') && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.ERROR, (event, data) => { console.error("HLS.js error:", data); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("HLS.js error: " + JSON.stringify(data)); }); } else if (videoUrl.endsWith('.m3u8') && video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoUrl; } // Handle video load errors video.onerror = () => { console.error("Video load error for URL:", videoUrl); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; debugLog("Video load error for URL: " + videoUrl); }; // Remove loading spinner when video is ready video.onloadedmetadata = () => { container.querySelector('.video-loading').remove(); debugLog("Video loaded successfully"); }; } catch (e) { console.error("Error loading video:", e); debugLog("Failed to load video: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading video. <a href="${videoUrl}" target="_blank" aria-label="Open video in new tab">Open video</a></p>`; } } // Lazy-load audio function loadAudio(button, audioUrl, containerId) { try { debugLog(`Loading audio for ${containerId}: ${audioUrl}`); if (!audioUrl) { const container = document.getElementById(containerId); container.innerHTML = `<p class="text-gray-500">No audio available</p>`; button.remove(); debugLog("No audio URL provided"); return; } const container = document.getElementById(containerId); container.innerHTML = ` <audio controls class="w-full max-w-[600px]" preload="metadata" aria-label="Audio explanation"> <source src="${audioUrl}" type="audio/mpeg"> Your browser does not support the audio tag. </audio> `; container.classList.add('active'); button.remove(); // Handle audio load errors const audio = container.querySelector('audio'); audio.onerror = () => { console.error("Audio load error for URL:", audioUrl); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; debugLog("Audio load error for URL: " + audioUrl); }; debugLog("Audio loaded successfully"); } catch (e) { console.error("Error loading audio:", e); debugLog("Failed to load audio: " + e.message); const container = document.getElementById(containerId); container.innerHTML = `<p class="text-red-500">Error loading audio. <a href="${audioUrl}" target="_blank" aria-label="Open audio in new tab">Open audio</a></p>`; } } // Toggle dark mode function toggleTheme() { try { debugLog("Toggling theme"); document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); debugLog("Theme toggled successfully"); } catch (e) { console.error("Error toggling theme:", e); debugLog("Failed to toggle theme: " + e.message); } } // Load theme preference function loadTheme() { try { debugLog("Loading theme preference"); const theme = localStorage.getItem('theme'); if (theme === 'dark') { document.documentElement.classList.add('dark'); } debugLog("Theme loaded successfully"); } catch (e) { console.error("Error loading theme:", e); debugLog("Failed to load theme: " + e.message); } } // Initialize on DOM content loaded window.addEventListener('DOMContentLoaded', () => { try { debugLog("DOM content loaded, initializing quiz"); loadTheme(); initQuiz(); } catch (e) { console.error("Error during DOMContentLoaded:", e); debugLog("Failed to initialize on DOMContentLoaded: " + e.message); document.getElementById('error-message').innerHTML = "Error initializing quiz. Please check the console for details or contact support."; document.getElementById('error-message').classList.remove('hidden'); } }); </script> </body> </html>" frameborder="0" width="100%" height="2000px">